ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • thread - join()
    프로그래밍/Java 2024. 7. 28. 14:42

    1. 스레드의 상태 및 전이 과정


    Join() 을 알아보기 전에 스레드의 상태에 대한 이해가 필요하다.

     

     

    스레드의 상태

    1) new (새로운 상태) : 스레드가 생성되었으나 아직 시작되지 않은 상태.

    2) Runnable (실행 가능 상태) : 스레드가 실행 중이거나 실행될 준비가 된 상태

    3) 일시 중지 상태들 (Suspended State) 

    - Block (차단 상태) : 스레드가 동기화 락을 기다리는 상태

    - Waiting (대기 상태) : 스레드가 무기한으로 다른 스레드의 작업을 기다리는 상태

    - Time Waiting (시간 제한 대기 상태) : 스레드가 일정 시간 동안 다른 스레드의 작업을 기다리는 상태

    4) Terminated (종료 상태) : 스레드의 실행이 완료된 상태

     

     

    자바 스레드 상태 전이 과정

    1) New → Runnable : start() 메서드를 호출하면 Runnable 상태로 전이된다.

    2) Runnable → Blocked / Waiting / Timed Waiting : 락을 얻지 못하거나 wait(), sleep() 메서드를 호출할 때 해당 상태로 전이된다.

    3) Blocked / Waiting / Timed Waiting → Runnable : 스레드가 락을 얻거나 기다림이 완료되면 Runnable 상태로 돌아간다.

    4) Runnable → Terminated : 스레드의 run() 메서드가 완료되면 스레드는 Terminated 상태가 된다.

     

     

    2.  join() 사용 전, 직면하는 문제 


    import static util.MyLogger.log;
    import static util.ThreadUtils.sleep;
    public class JoinMainV1 {
        public static void main(String[] args) {
            log("Start");
            SumTask task1 = new SumTask(1, 50);
            SumTask task2 = new SumTask(51, 100);
            Thread thread1 = new Thread(task1, "thread-1");
            Thread thread2 = new Thread(task2, "thread-2");
            
            thread1.start();
            thread2.start();
            
            log("task1.result = " + task1.result);
            log("task2.result = " + task2.result);
            int sumAll = task1.result + task2.result;
            log("task1 + task2 = " + sumAll);
            log("End");
    	}
        static class SumTask implements Runnable {
            int startValue;
            int endValue;
            int result = 0;
            public SumTask(int startValue, int endValue) {
                this.startValue = startValue;
                this.endValue = endValue;
    	}
        
        @Override
        public void run() {
            log("작업 시작");
            sleep(2000);
            int sum = 0;
            for (int i = startValue; i <= endValue; i++) {
        	    sum += i; 
            }
            result = sum;
            log("작업 완료 result=" + result);
        }
    }}

     

    실행결과

    15:36:28.347 [main] Start 
    15:36:28.349 [thread-1] 작업 시작 
    15:36:28.349 [thread-2] 작업 시작
    15:36:28.352 [main] task1.result = 0
    15:36:28.352 [main] task2.result = 0
    15:36:28.352 [main] task1 + task2 = 0
    15:36:28.352 [main] End
    15:36:30.355 [thread-1] 작업 완료 result=1275
    15:36:30.355 [thread-2] 작업 완료 result=3775

     

    실행 결과를 보면 task1.result, task2.result, task1 + task2 모두 0으로 나온다. 

     

    main 스레드는 두 스레드를 시작한 다음 task1.result, task2.result를 통해 인스턴스에 있는 결과값을 조회한다. 다른 스레드를 실행만 해두고 자신의 코드를 실행 한 것이다. 

     

    man 스레드에서 결과값을 보고 싶으면 thread-1, thread-2 계산이 끝날 때 까지 기다려야 한다.

     

     

    ... 

     

     

    위 문제와 같이 특정 스레드를 기다리는 가장 간단한 방법은 sleep()을 사용하는 것이다. 위 예제 코드에 sleep을 적용해보자.

     

    thread1.start();
    thread2.start();
    
    // 정확한 타이밍을 맞추어 기다리기 어려움
    log("main thread sleep()");
    sleep(3000);
    log("main thread 깨어남");
    
    
    log("task1.result = " + task1.result);
    log("task2.result = " + task2.result);
    int sumAll = task1.result + task2.result;
    log("task1 + task2 = " + sumAll);
    log("End");

     

     

    thread-1, thread-2 계산에 2초 정도의 시간이 걸린다. 그래서 main 스레드가 3초 후에 계산 결과를 조회하도록 하였다.

     

    하지만 이런 방법은 수행시간이 계속해서 달라질 경우 대기 시간에 손해를 보게 된다.

     

    물론, thread-1, thread2의 상태가 terminated 상태가 될 때까지 무한 루프로 체크하는 방법도 있다.

    - while(thread.getStatus() != TERMINATED) 

     

    이 또한 CPU 연산을 사용하는 것이기 때문에 올바른 방법은 아니다. 

     

    이때 Join() 메서드를 사용하면 깔끔하게 문제를 해결할 수 있다.

     

     

    ... 

     

     

    3.  join() 사용하여 문제 해결


    public static void main(String[] args) throws InterruptedException {
        log("Start");
        SumTask task1 = new SumTask(1, 50);
        SumTask task2 = new SumTask(51, 100);
        Thread thread1 = new Thread(task1, "thread-1");
        Thread thread2 = new Thread(task2, "thread-2");
        thread1.start();
        thread2.start();
        
        // 스레드가 종료될 때 까지 대기
        log("join() - main 스레드가 thread1, thread2 종료까지 대기"); 
        thread1.join();
        thread2.join();
        log("main 스레드 대기 완료");
    
        log("task1.result = " + task1.result);
        log("task2.result = " + task2.result);
        int sumAll = task1.result + task2.result;
        log("task1 + task2 = " + sumAll);
        log("End");
    }

     

    main 스레드에서 join 을 사용하게 되면 thread-1, thread-2 가 종료될 때 까지 기다린다. 

     

    이때 main 스레드는 WAITING 상태가 된다. 이후 thread-1 종료(TERMINATED) 되면 main 스레드는 RUNNABLE 상태가되고 다음 코드로 이동한다.

     

    이렇듯 특정 스레드가 완료될 때 까지 기다려야 하는 상황이라면 join()을 사용하자.

     

     

    ...

     

     

    join()의 단점은 스레드가 완료될 때 까지 무기한 기다리는 점이다.

     

    이를 위해선 인자로 ms 를 설정해주어야 한다. (ex join(ms)) ms 를 설정하면, main 스레드 상태는 WAITING 이 아닌 TIMED_WAITING 상태가 된다.

     

     

    ...

     

     

    정리

     

    1) main 스레드에서 다른 스레드를 실행할 땐 join() 메서드를 활용하자.

    2) join 메서드를 활용하면 메인 thread 에서 실행한 thread 가 종료될 때까지 기다린다.

    3) 이때 main 스레드는 WAITING 상태가 된다. 

    4) join 메서드의 단점은 무기한 기다릴 수도 있게 된다는 점이다.

    5) 만약 특정 시간만 기다리고 싶다면 join 메서드에 ms 인자를 설정하자.

    5) 이 경우 main 스레드는 TIMED_WAITING 상태가 된다.

     

Designed by Tistory.