우선, join()을 설명하기전 사용유무에 대한 차이점을 알아보기 위해서, 사용하지 않은 코드부터 보자.


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);
        }
    }
}

위의 코드를 보면 , thread1,thread2를 start() 하고 , join() 을 사용하지 않았다. 출력결과는 어떻게 될까 ? 과연 의도한대로 sumAll변수에 각각의 쓰레드에서 더한값의 결과가 담기게될까 ?

출력결과
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

출력결과를 보면 , 기대와는 다르게 메인쓰레드에서 호출한 thread1,thread2 모두 0으로 나오고 , 각각의 쓰레드에서만 결과가 제대로 나왔다. 위의 결과가 나온 상황을 아래의 그림을 통해 보겠다.

시간의 흐름으로 봤을 때

main 스레드는 thread-1 , thread2 에 작업을 지시하고, thread-1 , thread2 가 계산을 완료하기도 전에 먼저 계산 결과를 조회했다. 참고로 thread-1 , thread-2 가 계산을 완료하는데는 2초 정도의 시간이 걸린다(코드를 잘보면 sleep 2초를 설정했다). 따라서 결과가 task1 + task2 = 0 으로 출력된다.

메모리 구조로 봤을 때

main 스레드는 두 스레드를 시작한 다음에 바로 task1.result , task2.result 를 통해 인스턴스에 있는 결과 값을 조회한다. 참고로 main 스레드가 실행한 start() 메서드는 스레드의 실행이 끝날 때 까지 기다리지 않는다! 다른 스레드를 실행만 해두고, 자신의 다음 코드를 실행할 뿐이다!

의문이 들어야하는점
의문점
여기서 문제의 핵심은 main 스레드가 thread-1 , thread-2 의 계산이 끝날 때 까지 기다려야 한다는 점이다. 그럼 어떻게 해야 main 스레드가 기다릴 수 있을까?

join() 사용

아래의 코드는 join()을 사용한 코드이다.


public class JoinMainV2 {
    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");
    }

    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);
        }
    }
}

앞서 join() 을 사용하기전에는 sumAll 변수에 원하는 값이 나오지않았다. join() 을 사용한 코드는 과연 sumAll에 원하는 값이 담기게될까 ?

실행결과🔽
16:46:54.788 [ main] Start
16:46:54.790 [ thread-1] 작업 시작
16:46:54.790 [ thread-2] 작업 시작
16:46:54.790 [ main] join() - main 스레드가 thread1, thread2 종료까지 대기
16:46:56.801 [ thread-2] 작업 완료 result = 3775
16:46:56.801 [ thread-1] 작업 완료 result = 1275
16:46:56.802 [ main] main 스레드 대기 완료
16:46:56.803 [ main] task1.result = 1275
16:46:56.803 [ main] task2.result = 3775
16:46:56.804 [ main] task1 + task2 = 5050
16:46:56.804 [ main] End

실행 결과를 보면 정확하게 5050 이 계산된 것을 확인할 수 있다.

시간의 흐름으로 봤을 때

main 스레드에서 다음 코드를 실행하게 되면 main 스레드는 thread-1 , thread-2 가 종료될 때 까지 기다린다. 이때 main 스레드는 WAITING 상태가 된다.

thread1.join(); <- main 쓰레드 WAITING 상태
thread2.join(); <- main 쓰레드 WAITING 상태

예를 들어서 thread-1 이 아직 종료되지 않았다면 main 스레드는 thread1.join() 코드 안에서 더는 진행하지않고 멈추어 기다린다. 이후에 thread-1 이 종료되면 main 스레드는 RUNNABLE 상태가 되고 다음 코드로 이동한다.
이때 thread-2 이 아직 종료되지 않았다면 main 스레드는 thread2.join() 코드 안에서 진행하지 않고 멈추어 기다린다. 이후에 thread-2 이 종료되면 main 스레드는 RUNNABLE 상태가 되고 다음 코드로 이동한다.

join의 단점

하지만 join() 의 단점은 다른 스레드가 완료될 때 까지 무기한 기다리는 단점이 있다. 비유를 하자면 맛집에 한 번 줄을 서면 중간에 포기하지 못하고 자리가 날 때 까지 무기한 기다려야 한다. 만약 다른 스레드의 작업을 일정 시간 동안만
기다리고 싶다
면 어떻게 해야할까?

join - 특정 시간 만큼만 대기

join() 은 두 가지 메서드가 있다.

  • join() : 호출 스레드는 대상 스레드가 완료될 때 까지 무한정 대기한다.
  • join(ms) : 호출 스레드는 특정 시간 만큼만 대기한다. 호출 스레드는 지정한 시간이 지나면 다시 RUNNABLE 상태가 되면서 다음 코드를 수행한다.
profile
Live the moment for the moment.

0개의 댓글