Java/김영한의 실전자바

스레드 - join()

슈코 2024. 8. 26. 21:51

join()

특정 스레드가 다른 스레드들의 작업이 끝난 후, 무언가를 종합해서 하고 싶다면 join() 메소드를 사용해주면 된다.

 

ex)

  • 스레드1 - 1 ~ 50의 합을 구한다
  • 스레드2 - 51 ~ 100의 합을 구한다
  • 메인 - 스레드1,2의 결과값의 합을 구한다

 

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, "task1");
        Thread thread2 = new Thread(task2, "task2");

        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에게 각각 task1, task2를 시키고 그 결과를 합한다.

하지만, 실행해보면 원하는 결과값이 나오지 않는다.

 

 

main은 thread1과 thread2에게 작업을 시키고, 그것을 기다리지 않는다.

main은 바로 main의 일을 이어서 진행하기 떄문에, 빈값이 찍히게 된다.

 

여기서, thread1과 thread2는 각각의 스택에 run() 메소드를 올리고 실행한다.
task1, task2는 힙 영역에 할당되어서 공유하는 방식이다. 

 

스레드는 메서드의 호출을 관리하는데,
메서드 단위로 스택 프레임을 만들고, 그 위에 스택을 쌓아 올린다.
인스턴스의 메소드 호출 시, 해당 인스턴스의 참조값을 저장하는데 이를 'this'라고 한다.

 

main이 thread1과 thread2의 작업을 기다리기 위해서 join()을 사용해주면 된다.

 

// 스레드가 종료될 때 까지 대기
log("join() - main 스레드가 thread1, thread2 종료까지 대기");
thread1.join();
thread2.join();
log("main 스레드 대기 완료");

 

다음 join() 메소드를 thread1과 thread2의 스레드 시작(start)후에 넣어주면 다음과 같은 결과가 나온다.

 

join()의 영향으로 main은 thread1, thread2의 종료까지 대기한다.

해당 스레드가 종료되면, main은 다시 RUNNABLE 상태가 되어서 원래 임무를 수행한다.

 

join() - 특정 시간 대기

대상 스레드가 완료될 때 까지 기다리는건 비효율적일 수 있다.

join(ms)를 활용하면, 특정 스레드의 완료까지 대기하지만 입력한 시간이 지나면 빠져나오도록 할 수 있다.

 

thread1.join(1000);

이는 '메인 스레드가 thread1의 종료까지 1초동안 대기한다.' 라고 생각하면 된다.

 

끝날때까지 기다려야 하는 로직을 작성해야하면 join()을, limit time을 지정해줘야 하면 join(ms)를 활용하면 된다.

중간에 나올때, 결과가 없으면 그에 맞는 추가 로직을 작성해야 한다.

 

sleep()을 활용하기엔, 스레드가 정확히 얼마만큼의 대기와 스케줄링이 걸리는지 100% 장담할 수 없다.

 

예제를 통해서

다음과 같이 3초동안 작업을 하는 스레드가 있다.

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new MyTask(), "t1");
    Thread t2 = new Thread(new MyTask(), "t2");
    Thread t3 = new Thread(new MyTask(), "t3");

    t1.start();
    t1.join();

    t2.start();
    t2.join();

    t3.start();
    t3.join();

    System.out.println("모든 스레드 실행 완료");
}

public static class MyTask implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            log(i);
            sleep(1000);
        }
    }
}

 

스레드 하나 당 join()을 걸어서 기다려주고, 해당 작업이 끝나야 다음 작업을 시작하도록 하고 있다.

이렇게 되면, 시간은 총 9초가 걸리게 된다(t1, t2, t3 각 3초씩)

 

동시에 처리하려면,

모든 join() 메소드는 모든 스레드의 start() 메소드 이후에 실행해주어야 한다.

 

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new MyTask(), "t1");
    Thread t2 = new Thread(new MyTask(), "t2");
    Thread t3 = new Thread(new MyTask(), "t3");

    t1.start();
    t2.start();
    t3.start();

    t2.join();
    t1.join();
    t3.join();

    System.out.println("모든 스레드 실행 완료");
}

 

다음과 같이, 실행해주면 3초의 시간에 모든 작업이 끝나게 된다.

 

하지만, 항상 아래와 같은 방식으로 해줘야하는건 아니다.

만약 전 작업이 끝나야 다음 작업을 수행하는 경우라면 아래의 방법을 사용해주어야 한다.

 ( 그렇지만... 모든 작업이 끝나야 다음 작업을 실행하는 경우라면, 굳이 멀티스레드를 도입해서 복잡성을 높일 필요는 없을거 같다! )

'Java > 김영한의 실전자바' 카테고리의 다른 글

스레드 - interrupt()  (0) 2024.09.03
스레드 제어  (0) 2024.08.22
스레드의 생성과 실행  (0) 2024.08.15
프로세스와 스레드  (0) 2024.08.13
[김영한의 실전 자바 - 기본편] 기본형과 참조형  (0) 2024.01.21