1편에 이어 join() / wait() / notify() / stop() 에 관한 이야기를 이어가겠습니다.
join() (실행 -> 일시정지)
상황에 따라 스레드가 잠깐 멈췄다가 다른 스레드가 종료 된 후 이어서 실행해야 되는 경우가 있다. 예를 들어 ThreadA가 ThreadB에 의해 계산된 결과값을 사용하고 싶다고 하자. 이 경우 ThreadA는 ThreadB가 종료될 때까지 기다려야 한다. 이경우 ThreadA의 영역에 ThreadB의 join() 메소드를 호출하면 ThreadB가 종료될때 까지 기다리게 된다.
public class SumThread extends Thread {
public int sum = 0;
public int getSum() { return this.sum; }
public void setSum(int sum_ { this.sum = sum; }
@Override
public void run() {
for (int i=0; i<=10; ++i) {
this.sum += i;
}
}
}
public class Main {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
sumThread.start();
try {
sumThread.join();
} catch (InterruptedException e) {}
System.out.println("합계: " + sumThread.getSum());
}
}
sumThread는 0부터 10까지의 합을 구하는 스레드이다. 그리고 메인스레드는 이 합을 콘솔에 출력하려 한다. 이 경우 sumThread가 합을 구할때 까지 메인 스레드는 기다려야 하고, sumThread.join() 메소드를 호출시켜 일시정지 상태로 기다린다. 단, 이 경우 sumThread 실행 도중 interrupt 가 발생할 수 있으므로 try-catch 구문을 통해 예외를 처리시켜줘야 하한다.
wait() / notify() / notifyAll() : Producer-Consumer Problem
생산자-소비자 문제 (Producer-Conumser Problem)을 통해 3개의 상태 제어 메소드를 알아보고자 한다. 생산자-소비자 문제는 아주 유명한 동시성 제어 문제로, 한개의 컨테이너와 2개의 스레드(생산자 스레드와 소비자 스레드)로 구성된 문제이다. 생산자는 컨테이너에 데이터를 저장한다. 그러면 소비자는 데이터를 읽고 처리(삭제)를 하게 된다. 아주 단순해 보이는 이 프로그램은 2가지 큰 문제를 지니고 있다.
- 컨테이너에 데이터가 저장 되어 있는데, 생산자가 새로운 데이터를 저장하려는 경우
- 컨테이너에 데이터가 없는데, 소비자가 데이터를 읽고 처리하려는 경우
위 2문제가 발생하지 않기 위해서는 각 스레드가 다음의 규칙을 지켜야 한다.
< 데이터 저장 (Set Data) >
- 컨테이너에 데이터가 저장 되어 있는 경우, 생산자 스레드는 데이터가 소비될때 까지 기다린다.
- 컨테이너에 데이터를 저장한 경우, 소비자 스레드에게 알린다.
< 데이터 처리 (Get Data) >
- 컨테이너에 데이터가 비어 있는 경우, 소비자 스레드는 데이터가 저장될때까지 기다린다.
- 컨테이너의 데이터를 읽고 처리한 경우, 생산자 스레드에게 알린다.
위 4가지 규칙에서 중요한 키워드는 '기다린다' 와 '알린다' 이다. 이 2가지 키워드는 각각 wait(), notify() 메소드로 구현된다. 먼저 wait() 메소드가 호출되면 해당 스레드는 일시정지가 된다. wait()에 의해 일시정지 된 스레드는 notify() 메소드가 호출 되면 다시 실행 대기 상태로 돌아간다. notifyAll() 메소드는 모든 wait()에 의해 정지된 스레드를 실행 대기 상태로 되돌린다. 이 두개의 메소드를 통해 코드 레벨로 위 규칙을 다시 설명하면 다음과 같다.
< 데이터 저장 (Set Data) >
- (container != null) => wait()
- container = xxxData, notify()
< 데이터 처리 (Get Data) >
- (container == null) => wait
- return container, notify()
public class Container() {
private String data = null;
public void setData(String data) {
if (data != null) {
wait();
}
this.data = data;
notify();
}
public String getData() {
if (data == null) {
wait();
}
String retStr = this.data;
this.data = null;
notify();
return retStr;
}
}
스레드의 안전한 종료
스레드는 run() 메소드 블럭 내의 코드를 모두 실행되면 자동으로 종료가 된다. 하지만 상황에 따라 스레드를 즉시 종료하여야 할때가 있다.
- 위험한 종료 : stop()
실행(running)중인 스레드를 종료(terminated) 상태로 즉시 보내 주는 메소드로 stop()이 존재한다. 하지만 stop() 메소드는 말 그대로 스레드를 즉시 종료 시켜, 남아있는 자원들에 대한 처리가 되지 않아 큰 문제를 야기할 수 있다. 이 이유로 stop() 메소드는 deprecated 되었다. (쓰지 말라는 뜻)
즉, 스레드의 안전한 종료란 스레드를 종료할때, 남아있는 자원을 정리하여 종료 시 오류 등을 발생시키지 않게 하는 것을 의미한다.
스레드의 안전한 종료 1 : stop flag
boolean 필드 stop을 이용해 특정 조건을 만족할 경우(stop = true), 자원을 정리하고 안전하게 종료하는 방법이 있다.
public class ExampleThread() {
private boolean stop = false;
public void run() {
while (!stop) {
// 실행 코드
...
}
// 자원 정리를 위한 코드
}
}
스레드의 안전한 종료 2 : interrupt()
스레드의 외부에서 해당 스레드를 종료하고 싶을때는 해당 스레드의 interrupt() 메소드를 호출하면 된다. interrupt() 메소드는 스레드가 일시 정지 상태가 되면 스레드에게 intteruptedException을 발생시키게 된다. interruptedException이 발생한 스레드는 try-catch 구문을 통해 catch문 실행후 자원을 정리한 다음 스레드를 안전하게 종료한다.
interrupt() 메소드에서 주의할 점은 interruptedException은 오직 일시 정지 상태에서만 발생시킨다는 것이다. 즉, 스레드가 일시정지 상태로 가지 않는다면 interrupt() 메소드는 실행되지 않는다. 그러므로 인위적으로 스레드를 일시 정지 상태로 보내줄 필요가 존재하고, 위 예제에서는 ThreadB가 sleep() 메소드를 호출해 일시 정지 상태로 가서 interrupt()에 의해 종료된다.
'Java > Thread' 카테고리의 다른 글
[Java] 멀티 스레드(Multi Thread) (6) : 스레드풀의 작업의 생성과 처리 (0) | 2021.12.29 |
---|---|
[Java] 멀티 스레드(Multi Thread) (5) : 스레드풀의 생성과 종료 (0) | 2021.12.10 |
[Java] 멀티 스레드(Multi Thread) (3) - 스레드 상태 제어 1 (0) | 2021.12.04 |
[Java] 멀티 스레드(Multi Thread) (2) : 동기화(Synchronization) (0) | 2021.12.02 |
[Java] 멀티 스레드(Multi Thread) (1) : 멀티 스레드 생성과 실행 (0) | 2021.12.01 |