https://m.blog.naver.com/qbxlvnf11/221106007424
경우에 따라서는 두 개의 스레드를 교대로 번갈아가며 실행해야 할 경우가 있습니다.
이때, 스레드 간 협업이 잘 이루어지기 위해서는 wait(), notify(), notifyAll() 세 가지 메소드를 적절히 사용해야 합니다.
스레드 간 협업이 잘 이루어지기 위한 핵심은 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 풀어주고, 자신은 일시 정지 상태로 만들면서 공유 객체(두 개 이상의 스레드가 공유하는 객체)를 잘 컨트롤하는 것입니다.
공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 구분해 놓습니다.
한 스레드가 작업을 완료하면 notify() 메소드를 호출해서 일시 정지 상태에 있는 다른 스레드를 실행 대기 상태로 만들고, 자신은 두 번 작업을 하지 않도록 wait() 메소드를 호출하여 일시 정지 상태로 만듭니다.
아래 코드에서 이를 확인할 수 있습니다.
공유 객체인 WorkObject 클래스의 methodA()와 methodB()에 두 스레드의 작업을 정의해 두고, 두 스레드 ThreadA와 ThreadB가 교대로 methodA()와 methodB()를 호출하도록 하였습니다.
class WorkObject{ // 공유 객체 클래스public synchronized void methodA(){ System.out.println("ThreadA의 methodA() 작업 실행");notify(); // 일시 정지 상태에 있는 ThreadB를 실행 대기 상태로 전환try{wait(); // ThreadA를 일시 정지 상태로 전환}catch(InterruptedException e){}}public synchronized void methodB(){ System.out.println("ThreadB의 methodB() 작업 실행");notify(); // 일시 정지 상태에 있는 ThreadA를 실행 대기 상태로 전환try{wait(); // ThreadB를 일시 정지 상태로 전환}catch(InterruptedException e){}}}class ThreadA extends Thread{private WorkObject workObject;public ThreadA(WorkObject workObject){this.workObject = workObject; // 공유 객체 저장} @Override public void run(){for(int i=0; i<5; i++){ workObject.methodA(); // 공유 객체의 methodA() 호출}}}class ThreadB extends Thread{private WorkObject workObject;public ThreadB(WorkObject workObject){this.workObject = workObject; // 공유 객체 저장} @Override public void run(){for(int i=0; i<5; i++){ workObject.methodB(); // 공유 객체의 methodB() 호출}}}public class ThreadTest {public static void main(String args[]){ WorkObject sharedObject = new WorkObject(); // 공유 객체 생성 ThreadA threadA = new ThreadA(sharedObject); ThreadB threadB = new ThreadB(sharedObject);// start threadA.start(); threadB.start();}}
결과: 공유 객체의 각 메소드들을 번갈아가면서 호출
만약 wait() 대신 wait(long timeout)이나, wait(long timeout, int nanos)를 사용하면 notify()를 호출하지 않아도 지정된 시간이 지나면 스레드가 자동적으로 실행 대기 상태가 됩니다.
notify() 메소드와 동일한 역할을 하는 notifyAll() 메소드도 있는데, notify()는 wait()에 의해 일시 정지된 스레드 중 한 개를 실행 대기 상태로 만들고, notifyAll() 메소드는 wait()에 의해 일시 정지된 모든 스레드들을 실행 대기 상태로 만듭니다.
이 메소드들은 동기화 메소드 또는 동기화 블록 내에서만 사용할 수 있습니다.
이제 wait(), notify(), notifyAll() 메소드를 사용해서 스레드 간의 협업을 하는 방법에 대해 어느 정도 이해가 됐을 것입니다.
이 방법을 응용하여 데이터를 생성하고 소비하는 스레드 협업을 한 번 구현해 봅시다.
공유 객체 1개, 공유 객체의 데이터를 생성하는 생성자 스레드 1개와 공유 객체의 데이터를 받아오는 소비자 스레드 1개를 사용하여 구현할 것입니다.
구조는 다음과 같습니다.
class Data { // 공유 객체private String data;public synchronized void getData() {// data 필드가 null이면 소비자 스레드를 일시 정지 상태로if (this.data == null) {try {wait();} catch (InterruptedException e) {}} System.out.println("ConsummerThread가 읽은 데이터: " + this.data);// data 필드를 null로 만들고 생산자 스레드를 실행 대기 상태로 data = null;notify();}public synchronized void setData(String data){// data 필드가 null이 아니면 생산자 스레드를 일시 정지 상태로if(this.data != null){try{wait();} catch (InterruptedException e) {}}// data 필드에 값을 저장하고 소비자 스레드를 실행 대기 상태로this.data = data; System.out.println("ProducerThread가 생성한 데이터: " + this.data);notify();}}class ConsumerThread extends Thread{private Data data;public ConsumerThread(Data data){this.data = data;} @Override public void run(){for(int i = 1; i <= 5; i++){ data.getData();}}}class ProducerThread extends Thread{private Data data;public ProducerThread(Data data){this.data = data;} @Override public void run(){for(int i = 1; i <= 5; i++){ data.setData("Data=" + i);}}}public class ThreadTest{public static void main(String[]args){ Data data = new Data(); ProducerThread producerThread = new ProducerThread(data); ConsumerThread consumerThread = new ConsumerThread(data);// start producerThread.start(); consumerThread.start();}}
생산자 스레드와 소비자 스레드의 협업이 유동적으로 이루어지기 위해
생성자 스레드는 소비자 스레드가 읽기 전에 새로운 데이터를 두 번 생성하면 안 되고(setData() 메소드를 두 번 실행하면 안 됨), 소비자 스레드는 생성자 스레드가 새로운 데이터를 생성하기 전에 이전 데이터를 두 번 읽어서도 안 됩니다(getData() 메소드를 두 번 실행하면 안 됨).
이 조건은 공유 객체(Data)의 data 필드 값이 null이면 생산자 스레드를 실행 대기 상태로 만들고, 소비자 스레드를 일시 정지 상태로 만들며, 반대로 data 필드 값이 null이 아니면 소비자 스레드를 실행 대기 상태로 만들고, 생산자 스레드를 일시 정지 상태로 만드는 방법을 이용해서 충족할 수 있습니다.