Java - Thread 생성, 실행, 정지하는 방법

JS · 17 Jun 2020

Java에서 Thread를 생성하고, 실행, 정지하는 방법에 대해서 알아보겠습니다.

Thread 생성 및 실행

다음과 같이 Thread를 생성하고 실행할 수 있습니다.

Thread thread = new Thread();
thread.start();

위의 코드는 단순히 쓰레드만 생성합니다.

만약 어떤 코드가 동작하도록 만드려면 Thread를 상속받아 구현을 해야 합니다.

다음과 같이 Thread를 상속받고 run()을 오버라이딩하면 Thread가 실행될 때 run()이 호출됩니다.

public class MyThread extends Thread {

    public MyThread() {
    }

    @Override
    public void run() {
        log("Thread is running");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log("Finish run()");
    }
}

다음과 같이 위에서 구현한 Thread를 실행할 수 있습니다.

MyThread thread = new MyThread();
log("Start thread");
thread.start();

Output:

19:25:13.418 (main) Start thread
19:25:13.419 (Thread-0) Thread is running
19:25:16.419 (Thread-0) Finish run()

참고로 log()는 아래와 같이 시간과 쓰레드 이름을 함께 출력해주는 로그 메소드입니다.

public static void log(String msg) {
    System.out.println(LocalTime.now() + " ("
            + Thread.currentThread().getName() + ") " +  msg);
}

Thread, Runnable

Thread를 상속받아 구현하지 않고 Runnable을 구현한 객체를 Thread에 전달하여 실행하는 방법도 있습니다.

다음과 같이 Runnable을 구현할 수 있습니다.

public static class MyThreadRunnable implements Runnable {

    private final AtomicBoolean running = new AtomicBoolean(false);

    public MyThreadRunnable() {
    }

    @Override
    public void run() {
        log("Thread is running");
        log("Do something");
    }
}

그리고 아래와 같이 Thread의 인자로 Runnable을 전달해주면 thread가 실행될 때 Runnable에 구현된 run()이 실행됩니다.

MyThreadRunnable runnable = new MyThreadRunnable();
Thread thread = new Thread(runnable);

log("Start thread");
thread.start();

Output:

19:28:57.276 (main) Start thread
19:28:57.277 (Thread-0) Thread is running
19:28:57.277 (Thread-0) Do something

Join

main 쓰레드에서 다른 쓰레드의 작업이 모두 끝날 때 까지 기다릴 때도 있습니다. 이럴 때 join()을 사용할 수 있습니다.

아래 예제에서 join()을 호출하면, thread의 작업이 모두 완료되기 전까지 blocking되어 대기 상태가 됩니다. 작업이 완료되면 join()은 리턴되고 다음 코드를 실행하게 됩니다.

MyThread thread = new MyThread();
log("Start thread");
thread.start();

thread.join();
log("Everything is done");

Join, timeout 설정

Thread가 종료되지 않는 경우 join()도 무한히 대기할 수 있습니다. Timeout을 설정하여 일정 시간이 지나면 더 이상 기다리지 않도록 만들 수 있습니다.

Timeout은 join(miliseconds) 처럼 인자로 시간을 전달하여 설정할 수 있습니다.

MyThread thread = new MyThread();
log("Start thread");
thread.start();

thread.join(1000);
log("Everything is done");

결과를 보면 1초 내에 쓰레드가 완료되지 않으면 기다리지 않고 다음 코드를 수행하는 것을 알 수 있습니다.

22:08:41.365 (main) Start thread
22:08:41.366 (Thread-0) Thread is running
22:08:42.366 (main) Everything is done
22:08:44.366 (Thread-0) Finish run()

Thread 종료

Thread.stop()은 쓰레드를 안전하게 종료하지 못하여 더 이상 지원하지 않고 있습니다.(deprecated)

대신 flag 방식으로 쓰레드를 종료하도록 구현하거나, Interrupt 방식으로 쓰레드를 종료하도록 구현해야 합니다.

Flag 방식으로 쓰레드 종료

다음과 같이 Runnable을 구현하였습니다. 이 클래스에는 running이라는 객체가 있습니다. 이것을 on, off하여 쓰레드를 실행하거나 종료하는 의미로 사용할 것입니다.

public static class MyThreadRunnable implements Runnable {

    private final AtomicBoolean running = new AtomicBoolean(false);

    public MyThreadRunnable() {
    }

    public void stop() {
        log("Stop this thread");
        running.set(false);
    }

    public void run() {
        running.set(true);
        while (running.get()) {
            try {
                log("Thread is running");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Thread was interrupted");
            }
        }
    }
}

다음과 같이 Thread를 생성하고 실행하였습니다. 5초 뒤에 runnable.stop()을 호출하여 running을 false로 변경하여 run()이 끝나도록 만들었습니다.

MyThreadRunnable runnable = new MyThreadRunnable();
Thread thread = new Thread(runnable);
thread.start();

Thread.sleep(5000);

log("Send stop signal");
runnable.stop();

Output:

19:39:13.038 (Thread-0) Thread is running
19:39:14.039 (Thread-0) Thread is running
19:39:15.039 (Thread-0) Thread is running
19:39:16.039 (Thread-0) Thread is running
19:39:17.039 (Thread-0) Thread is running
19:39:17.946 (main) Send stop signal
19:39:17.946 (main) Stop this thread

run()이 모두 끝나면 쓰레드도 종료되기 때문에 이런식으로 구현할 수 있습니다.

Interrupt 방식으로 쓰레드 종료

이번에는 다음과 같이 Runnable을 구현하였습니다. runnable 플래그도 없고 실행되면 무한히 동작하게 됩니다.

public static class MyThreadRunnable implements Runnable {
    public MyThreadRunnable() {
    }

    public void run() {
        while (true) {
            try {
                log("Thread is running");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log("Thread was interrupted.");
                log("Release some resources");
                log("Done");
                return;
            }
        }
    }
}

다음과 같이 쓰레드를 실행하였습니다. 그리고 5초 뒤에 interrupt()를 호출하였습니다.

Thread thread = new Thread(new MyThreadRunnable());
thread.start();

Thread.sleep(5000);

log("isInterrupted(): " + thread.isInterrupted());

log("Interrupt this thread");
thread.interrupt();

log("isInterrupted(): " + thread.isInterrupted());

interrupt()가 호출되면 쓰레드의 동작하는 코드에서 InterruptedException가 발생합니다. 이 예외가 발생했을 때 자원을 정리하고 종료되도록 만들면 됩니다.

  • interrupt(): Interrupt 예외를 발생시킵니다.
  • isInterrupted(): Intrrupted 상태인지 결과를 boolean으로 리턴합니다.

Output:

19:46:10.010 (Thread-0) Thread is running
19:46:11.010 (Thread-0) Thread is running
19:46:12.011 (Thread-0) Thread is running
19:46:13.011 (Thread-0) Thread is running
19:46:14.011 (Thread-0) Thread is running
19:46:14.946 (main) isInterrupted(): false
19:46:14.946 (main) Interrupt this thread
19:46:14.946 (main) isInterrupted(): true
19:46:14.946 (Thread-0) Thread was interrupted.
19:46:14.946 (Thread-0) Release some resources
19:46:14.946 (Thread-0) Done
codechachaCopyright ©2019 codechacha