스레드 제어와 생명 주기

2024. 10. 2. 01:24개발/Java

스레드 그룹

thread.getThreadGroup() 하면 나오는게 스레드 그룹이다. 스레드 그룹은 스레드를 그룹화하여 관리할 수 있다. 기본적으로 모든 스레드는 부모 스레드와 동일한 스레드 그룹에 속하게 된다.

스레드 그룹은 여러 스레드를 하나의 그룹으로 묶어서 특정 작업(예: 일괄 종료, 우선순위 설정)을 수행할 수 있다.

스레드 상태

NEW : 스레드가 아직 시작되지 않은 상태

RUNNABLE : 스레드가 실행 중이거나 실행 가능한 상태

BLOCKED : 스레드가 동기화 락을 기다리는 상태

WAITING : 스레드가 다른 스레드의 특정 작업이 완료되기를 기다리는 상태

TIMED_WAITING : 일정 시간동안 기다리는 상태

TERMINATED : 스레드가 실행을 마친 상태

main 스레드가 thread1, thread2에 어떤 작업을 지시하고 그 결과를 받아서 처리하고 싶다면 어떻게 해야할까?

public class JoinMain {

    public static void main(String[] args) {
        SumTask task1 = new SumTask(1, 50);
        SumTask task2 = new SumTask(51, 100);

        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);

        thread1.start();
        thread2.start();

        int result1 = task1.result;
        int result2 = task2.result;
        log("result1 = " + result1);
        log("result2 = " + result2);
        int sumAll = result1 + result2;
        log("sumAll = " + sumAll);
    }

    static class SumTask implements Runnable {
        int startValue;
        int endValue;
        int result;

        public SumTask(int startValue, int endValue) {
            this.startValue = startValue;
            this.endValue = endValue;
        }

        @Override
        public void run() {
            for (int i = startValue; i <= endValue; i++) {
                result += i;
            }
        }
    }

}

join() 을 사용한다.

public static void main(String[] args) throws InterruptedException {
    SumTask task1 = new SumTask(1, 50);
    SumTask task2 = new SumTask(51, 100);

    Thread thread1 = new Thread(task1);
    Thread thread2 = new Thread(task2);

    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    int result1 = task1.result;
    int result2 = task2.result;
    log("result1 = " + result1);
    log("result2 = " + result2);
    int sumAll = result1 + result2;
    log("sumAll = " + sumAll);
}

main 스레드는 thread1이 TERMINATED 상태가 될 때까지 대기(WAITING 상태가 된다.)한다. thread1이 TERMINATED된 것을 확인하면 main 스레드는 RUNNABLE이 되고 thread2.join()이 실행되었으므로 thread2가 TERMINATED 될 때까지 WAITING 상태였다가 TERMINATED 된 것을 확인하면 RUNNABLE이 된다. 하지만 join은 다른 스레드가 종료될 때까지 무제한 기다려야 할 수도 있다. 따라서 join(int millis) 메서드를 사용하면 지정한 시간이 지나면 다시 RUNNABLE 상태가 된다. 이 떄 main 스레드의 상태는 TIMED_WAITING이 되었다가 RUNNABLE이 된다.

Interrupt

스레드가 sleep하고 있을 경우 어떻게 깨워야 할까? 바로 thread.interrupt()를 호출하면 된다. 그러면 thread는 TIMED_WAITING 상태에서 RUNNABLE 상태가 된다.

스레드가 sleep 하고 있을 경우만 interrupt()를 받아들일 수 있는데

다른 부분에서 interrupt()를 받아들이게 하려면 어떻게 해야하는가?

Thread.currentThread().isInterrupted()를 체크해주면 된다.

그리고 isInterrupted()는 예외가 발생하였을 때 false로 바꿔준다. 그 이유는? 스레드의 인터럽트 상태를 정상으로 돌리지 않으면 이후에도 계속 인터럽트가 발생하게 된다.

InterruptedException이 있는 catch문을 타야 Thread.getCurrentThread().isInterrupted가 true에서 false로 바뀌게 된다.

Thread.inturrpted() 와 Thread.*currentThread*().isInterrupted()의 차이?

  1. Thread.inturrpted()
    • 현재 스레드의 interrupt 상태를 확인하고 interrupt가 true일 경우 true를 반환하고 interrupt 상태를 false로 초기화 한다.
  2. Thread.*currentThread*().isInterrupted()
    • 현재 스레드의 interrupt 상태를 확인하지만 interrupt 상태를 변화시키진 않는다.

아래 예제 코드에서 그 차이를 느낄 수 있다.

public class ThreadInterruptMain {

    public static void main(String[] args) {

        Thread thread = new Thread(new PrintThread2());
        thread.start();
        log("메인 스레드 시작");
        try {
            Thread.sleep(1000);
            thread.interrupt();
            log("thread.isInterrupted() = " + thread.isInterrupted());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class PrintThread implements Runnable {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) { // 인터럽트 상태 변경X
                log("작업 중");
            }
            log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
            try {
                log("자원 정리 시도");
                Thread.sleep(1000);
                log("자원 정리 완료");
            } catch (InterruptedException e) {
                log("자원 정리 실패 - 자원 정리 중 인터럽트 발생");
                log("work 스레드 인터럽트 상태3 = " +
                    Thread.currentThread().isInterrupted());
            }
            log("작업 종료");
        }
    }

    static class PrintThread2 implements Runnable {

        @Override
        public void run() {
            while (!Thread.interrupted()) { // 인터럽트 상태 변경X
                log("작업 중");
            }
            log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
            try {
                log("자원 정리 시도");
                Thread.sleep(1000);
                log("자원 정리 완료");
            } catch (InterruptedException e) {
                log("자원 정리 실패 - 자원 정리 중 인터럽트 발생");
                log("work 스레드 인터럽트 상태3 = " +
                    Thread.currentThread().isInterrupted());
            }
            log("작업 종료");
        }
    }
}

PrintThread1으로 실행했을 때 로그

2024-10-02 01:21:00.443 [ Thread-0]: 작업 중
2024-10-02 01:21:00.443 [ Thread-0]: 작업 중
2024-10-02 01:21:00.446 [     main]: thread.isInterrupted() = true
2024-10-02 01:21:00.446 [ Thread-0]: work 스레드 인터럽트 상태2 = true
2024-10-02 01:21:00.446 [ Thread-0]: 자원 정리 시도
2024-10-02 01:21:00.446 [ Thread-0]: 자원 정리 실패 - 자원 정리 중 인터럽트 발생
2024-10-02 01:21:00.446 [ Thread-0]: work 스레드 인터럽트 상태3 = false
2024-10-02 01:21:00.446 [ Thread-0]: 작업 종료

PrintThread2로 실행했을 때 로그

2024-10-02 01:18:38.524 [ Thread-0]: 작업 중
2024-10-02 01:18:38.524 [ Thread-0]: 작업 중
2024-10-02 01:18:38.545 [     main]: thread.isInterrupted() = true
2024-10-02 01:18:38.599 [ Thread-0]: work 스레드 인터럽트 상태2 = false
2024-10-02 01:18:38.599 [ Thread-0]: 자원 정리 시도
2024-10-02 01:18:39.604 [ Thread-0]: 자원 정리 완료
2024-10-02 01:18:39.604 [ Thread-0]: 작업 종료

'개발 > Java' 카테고리의 다른 글

Mutex와 Semaphore  (0) 2024.10.20
메모리 가시성  (0) 2024.10.20
동기화  (0) 2024.10.05
JVM 메모리 영역  (0) 2024.09.29
멀티스레드와 동시성  (0) 2024.09.24