[Java] Thread와 상태제어
1. Thread의 상태
Thread는 프로그램 실행의 가장 작은 단위이다. 우리가 이전 글에서는 MultiThread를 구현하는것을 보았는데,
그냥 저냥 프로그램을 실행시키면 그냥 main thread에서 실행하게 된다.
이런 Thread도 항상 실행 될 수가 없는것이. Thread도 한개의 프로세스이기 때문에, JVM에서 동시에 처리하는것이 아닌 시간을 잘게 쪼갠 후에 여러개의 쓰레드를 순서를 두고 실행하는것을 알 수 있다.
우리가 CPU내 프로세스의 상태를 나타낼 때
new, ready, running, blocked, suspended등으로 나타낼 수 있는데
JVM의 Thread역시 이렇게 현재 상태에 따라 여러 단계로 나뉠 수 있다.
현재 실행되는 Thread는
NEW: 쓰레드 객체가 생성되었으나, 아직 start()메서드가 호출되지 않은 상태
Runnable: 실행대기 상태
Running: 실행중 상태
Blocked: 봉쇄 상태
1. WAITING: 다른 쓰레드가 통지할 때 까지 기다리는 상태
2. TIMED_WAITING: Timesleep 상태
3. BLOCKED: 락이 풀릴때까지 기다리는 상태
Dead: 쓰레드의 run메서드가 종료된 상태
로 나눌 수 있다.
2. join() 메서드
쓰레드가 멈출 때 까지 기다리게 한다.
코드를 우선 보자
public class joinTest extends Thread{
public void run(){
for(int i = 0; i < 5; i++){
System.out.println("joinTest : "+ i);
try {
Thread.sleep(500); //0.5초씩 휴식
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class JoinExam {
public static void main(String[] args) {
joinTest thread = new joinTest();
thread.start();
System.out.println("Thread가 종료될때까지 기다립니다.");
try {
thread.join(); //join을 통해 joinTest의 running상태가 지속
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread가 종료되었습니다.");
}
}
main에서 joinTest 클래스 객체를 만들고 실행시키는데,
main역시 위에서 또 하나의 thread라고 설명했다. 즉 thread join이 없을 때, 아래와 같은 flow를 가지게 되는데,
| 시간(JVM에서 쪼갠) | code | main | joinTest |
| ... | (thread.start) | running | runable |
| 1 | runable | running | |
| 2 | running | runable | |
| 3 | runable | running |
.join()이라는 메서드를 사용한 후에는 joinTest의 running이 dead상태로 가기 전 까지 joinTest(thread객체)를 실행한다.
| 시간(JVM에서 쪼갠) | code | main | joinTest |
| 1 | thread.start(); | running | runable |
| 2 | thread.join(); | running | runable |
| 3 | ... | WAITING(blocked) | running |
| 4 | .. | WAITING(blocked) | running |
| 5 | ... | WAITING(blocked) | running |
3. wait()과 notify() 메서드
wait과 notify는 Synchronized된 블록 안에서 사용해야 한다.
wait 를 만나게 되면 모니터링 락의 권한을 놓고 대기하게 된다.
notify를 만나게 되면, 모니터링 락의 권한을 다시 내려놓는것을 의미한다.
public class ThreadB extends Thread{
int total;
public void run(){
synchronized(this){ //해당 동기화 블록이 모니터링 락을 획득한다.
for(int i=0; i<5 ; i++){
total += i;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notify(); //main thread를 깨운다.
}
}
}
public class ThreadA {
public static void main(String[] args){
ThreadB b = new ThreadB();
b.start();
synchronized(b){
try{
System.out.println("b가 완료될때까지 기다립니다.");
b.wait();//wait을 만났다.
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Total: " + b.total);
}
}
}
표로 설명하면,
| time/code | main | ThreadB |
| ThreadB b = new ThreadB(); | running | NEW |
| b.start(); | running | runnable |
| synchronized(b){.. | running | runnable |
| b.wait(); | blocked (WAITING) | running |
| for();... //덧셈연산, threadB실행 | blocked (WAITING) | running |
| notify(); | running | dead |
| System.out.println("Total: ",b.total); | running | dead |
| dead | dead | |
wait 과 notify의 사용은 running 상태와 runnable상태로 쓰레드가 나뉘는것이 아니라, blocked 상태와 runnable, running 상태 간의 변화이다. wait()된 스레드는 반드시 notify()나 notifyAll()메소드를 호출하여 블록상태를 해제해 줄 필요가 있다는 점을 기억하자.

4. 데몬 쓰레드
데몬(Daemon)이란 보통 리눅스와 같은 유닉스계열의 운영체제에서 백그라운드로 동작하는 프로그램을 말한다.
쓰레드에 데몬을 설정하여 데몬 쓰레드로 바꾸어 주면 된다.
public class DaemonThread implements Runnable { //Runnable로 구현
public void run() {
while (true) {
System.out.println("데몬 쓰레드가 실행중입니다.");
try {
Thread.sleep(500); //0.5초씩 쉬면서 무한으로 데몬 쓰레드를 실행시킴
} catch (InterruptedException e) {
e.printStackTrace();
break; //Exception발생시 while을 탈출하게 함
}
}
}
public static void main(String[] args) {
Thread th = new Thread(new DaemonThread());
th.setDaemon(true); //th를 demon thread로 설정한다.
th.start(); //th실행
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("메인 쓰레드가 종료됩니다. "); //이때 Demonthread도 함께 종료됨
}
}
데몬쓰레드는 일반쓰레드(main)이 종료되면 강제적으로 종료되는 특징을 가지고 있다.
데몬쓰레드의 예로는 가비지컬랙션, 자동저장, 화면보호기 등이 있다.
실행 후 대기하고 있다가 어떤 조건이 만족되면 작업을 진행하고 작업이 종료되면 다시 대기하고 있는다.