[Java] Thread 와 상태제어 (2) : join, wait, notify

준우·2022년 5월 24일
0

Java

목록 보기
19/30
post-thumbnail

🫂 join()

join() 메소드는 실행중인 쓰레드를 강제로 실행 대기(lock) 상태로 변하게 한 뒤 특정 쓰레드가 실행되고 종료 될 때까지 기다리게 할 수 있다.

예시 코드

MyThread5 클래스

먼저, 아래 run() 메소드에서 sleep() 메소드를 이용해 0.5초씩 쉬면서 숫자를 출력하는 MyThread5 클래스를 작성했다.

    public class MyThread5 extends Thread{
        public void run(){
            for(int i = 0; i < 5; i++){
                System.out.println("MyThread5 : "+ i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } // run
    }

JoinExam 클래스

아래는 MyThread5 와 메인 쓰레드를 실행시키는 코드이다. 따라서 수행 흐름이 2개가 만들어졌다. 메인에서 시작과 종료 문구를 출력하도록 했다.

 public class JoinExam { 
        public static void main(String[] args) {
            MyThread5 thread = new MyThread5();
            thread.start(); 
            
            System.out.println("시작되었습니다.");
            System.out.println("종료되었습니다."); 
        }   
    }

해당 코드를 실행시키면 아래와 같이 메인 쓰레드가 시작, 종료라는 문구 출력을 이미 끝내는 동안 MyThread5는 준비를 마쳤다가 숫자를 출력하는 것을 확인할 수 있다.

join 메소드 사용

그런데 join() 메소드를 쓰면 메인 쓰레드가 MyThread5 쓰레드를 기다리게 할 수 있다. 아래 코드는 MyThread5 를 실행하고, 쓰레드가 종료될 때까지 기다린 후 내용을 출력하는 JoinExam 클래스이다.

 public class JoinExam { 
        public static void main(String[] args) {
            MyThread5 thread = new MyThread5();
            // Thread 시작 
            thread.start(); 
            System.out.println("Thread가 종료될때까지 기다립니다.");
            try {
                // 해당 쓰레드가 멈출때까지 멈춤
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread가 종료되었습니다."); 
        }   
    }

실행 결과

메인 쓰레드가 실행되었다가, MyThread가 실행되고 종료될 때까지 기다린 후에 메인쓰레드도 종료되는 것을 알 수 있다.

⏱️ wait(), 🔉 notify()

wait(), notify() 메소드는 동기화(synchronized) 된 블록 안에서 사용해야 한다. wait() 메소드를 만난 쓰레드는 해당 객체에 모니터 락에 대한 권한이 있었다면 모니터락 권한을 놓고 대기하게 된다.

아래는 Thread를 상속받는 ThreadB 클래스이다. 값을 누적할 수 있는 total 이라는 변수를 선언했으며, run 메소드를 오버라이딩 했다. 동기화를 위해 synchronized 블록 안에서 수행한다.

해당 쓰레드가 실행되면 자기 자신의 모니터 락 권한을 획득하게 된다. 총 다섯번 반복, 0.5초씩 쉬면서 total 값을 누적한다. 그 후 notify 메소드를 호출하고 대기 상태의 쓰레드를 깨운다.

public class ThreadB extends Thread{
       // 해당 쓰레드가 실행되면 자기 자신의 모니터링 락을 획득
       // 5번 반복하면서 0.5초씩 쉬면서 total에 값을 누적
       // 그후에 notify()메소드를 호출하여 wiat하고 있는 쓰레드를 깨움 
        int total;
        @Override
        public void run(){
            synchronized(this){
                for(int i=0; i<5 ; i++){
                    System.out.println(i + "를 더합니다.");
                    total += i;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                notify();
            }
        }
    }

그리고 아래는 ThreadB를 사용하며, wait() 메소드를 호출하는 ThreadA 클래스이다. ThreadB의 b 객체를 생성한 뒤, b에 대한 동기화 블럭을 설정하고 메인이 동기화 블록 안의 코드를 b보다 먼저 실행한다면 대기하도록 한다.

public class ThreadA {
        public static void main(String[] args){
            // 앞에서 만든 쓰레드 B를 만든 후 start 
            // 해당 쓰레드가 실행되면, 해당 쓰레드는 run메소드 안에서 자신의 모니터링 락을 획득
            ThreadB b = new ThreadB();
            b.start();

            // b에 대하여 동기화 블럭을 설정
            // 만약 main쓰레드가 아래의 블록을 위의 Thread보다 먼저 실행되었다면 wait를 하게 되면서 모니터링 락을 놓고 대기       
            synchronized(b){
                try{
                    // b.wait()메소드를 호출.
                    // 메인쓰레드는 정지
                    // ThreadB가 5번 값을 더한 후 notify를 호출하게 되면 wait에서 깨어남
                    System.out.println("b가 완료될때까지 기다립니다.");
                    b.wait();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }

                //깨어난 후 결과를 출력
                System.out.println("Total is: " + b.total);
            }
        }
    }

🙏 Reference

0개의 댓글