스레드 객체를 생성하고 start() 메소드를 호출하면 바로 실행되는 것이 아니라 실행 대기 상태가 된다. 실행 대기 상태란 언제든지 실행하 준비가 되어 있는 상태를 말한다. 운영체제는 실행 대기 상태에 있는 스레드 중에서 하나를 선택해서 실행 상태로 만든다.
실행 상태의 스레드는 run() 메소드를 모두 실행하기 전에 다시 실행 대기 상태로 돌아갈 수 있으며, 실행 대기 상태에 있는 다른 스레드가 선택되어 실행 상태가 되기도 한다.
실행 상태에서 run() 메소드의 내용이 모두 실행되면 스레드의 실행이 멈추고 종료 상태가 된다.
이 과정은 아래와 같다.
start() 반복
스레드 객체 생성 ----------> 실행 대기 <--------> 실행 ------------> 종료
NEW RUNNABLE TERMINATED
스레드 상태
스레드 객체를 생성하고 start() 메소드를 호출하면 곧바로 스레드가 실행되는 것처럼 보이지만 사실은 실행 대기 상태가 된다. 실행 대기 상태란 실행을 기다리고 있는 상태를 말한다. 실행 대기 상태에 있는 스레드 중에서 운영체제는 하나의 스레드를 선택하고 CPU(코어)가 run() 메소드를 실행하도록 한다. 이때를 실행 상태 라고한다.
실행 상태의 스레드는 run() 메소드를 모두 실행하기 전에 다시 실행 대기 상태로 돌아갈 수 있다. 그리고 실행 대기 상태에 있는 다른 스레드가 선택되어 실행 상태가 된다.
이렇게 스레드는 실행 대기 상태와 실행 상태가 번갈아가면서 자신의 run() 메소드를 조금씩 실행한다. 실행 상태에서 run() 메소드가 종료되면, 더 이상 실행할 코드가 없기 때문에 스레드의 실행은 멈추게 된다. 이 상태를 종료 상태 라고 한다.
이처럼 스레드는 실행 대기 상태와 실행 상태를 번갈아 변하면서, 경우에 따라서 실행 상태에서 일시 정지 상태로 가기도 한다. 일시 정지 상태는 스레드가 실행할 수 없는 상태이다. 일시 정지 상태에서는 바로 실행 상태로 돌아갈 수 없고, 일시 정지 상태에서 빠져나와 실행 대기 상태로 가야한다.
스레드 상태 제어
사용자는 미디어 플레이어에서 동영상을 보다가 일시 정지할 수도 있고, 종료할 수도 있다. 일시 정지는 조금 후 다시 동영상을 보겠다는 의미이므로 미디어 플레이어는 동양상 스레드를 일시 정지 상태로 만들어야 한다. 그리고 종료는 더 이상 동영상을 보지않겠다는 의미이므로 미디어 플레이어는 스레드를 종료 상태로 만들어야 한다. 이와 같이 실행중인 스레드의 상태를 변경하는 것을 스레드 상태 제어 라고 한다.
멀티 스레드 프로그램을 만들기 위해서는 정교한 스레드 상태 제어가 필요한데, 상태 제어가 잘못되면 프로그램은 불안해져서 먹통이 되거나 다운된다. 스레드 제어를 제대로 하기 위해서는 스레드의 상태 변화를 가져오는 메소드를 파악하고 있어야 한다.
다음은 상태 변화를 가져오는 메소드의 종류를 보여준다
| 메소드 | 설명 |
| interrupt() | 일시 정지 상태의 스레드에서 InterruptedException을 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다. |
| sleep(long millis) | 주어진 시간 동안 스레들르 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. |
| stop() | 스레드를 즉시 종료한다. 불안전한 종료를 유발하므로 사용하지 않는 것이 좋다. |
주어진 시간 동안 일시 정지
실행 중인 스레드를 일정 시간 멈추게 하고 싶다면 Thread 클래스의 정적 메소드인 sleep()을 사용하면 된다. 아래와 같이 Thread.sleep() 메소드를 호출한 스레드는 주어진 시간 동안 일시 정지 상태가 되고, 다시 실행 대기 상태로 돌아간다.
try{
Thread.sleep(1000);
}catch(InterruptedException e) {
//interrupt() 메소드가 호출되면 실행
}
매개값에는 얼마 동안 일시 정지 상태로 있을 것인지 밀리세컨드(1/1000초) 단위로 시간을 주면 된다. 위와 같이 1000이라는 값을 주면 1초가 경과할 동안 일시 정지 상태로 있게 된다. 일시 정지 상태에서 주어진 시간이 되기 전에 interrupt() 메소드가 호출되면 InterruptedException 이 발생하기 때문에 예외 처리가 필요하다.
스레드의 안전한 종료
스레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료된다. 하지만 경우에 따라서는 실행 중인 스레드를 즉시 종료해야할 때가 있다. 예를 들어 동영상을 끝까지 보지 않고, 사용자가 멈춤을 요구할 수 있다.
Thread는 스레드를 즉시 종료하기 위해서 stop() 메소드를 제공하고 있는데, 이 메소드는 deprecated(중요도가 떨어져 이제 사용되지 않음)이 되었다. 그 이유는 stop() 메소드로 스레드를 갑자기 종료하게 되면 스레드가 사용 중이던 자원들이 불안전한 상태로 남겨지기 때문이다.
stop 플래그를 이용하는 방법
스레드는 run() 메소드가 끝나면 자동적으로 종료되므로, run() 메소드가 정상적으로 종료되도록 유도하는 것이 중요하다. 아래 코드는 stop 플래그를 이용해서 run() 메소드의 종료를 유도한다.
public class XXXThread extends Thread{
private boolean stop; // stop 플래그 필드
public void run() {
while(!stop){ <-- stop이 true가 되면 run() 종료
스레드가 반복 실행하는 코드;
}
// 스레드가 사용한 자원 정리
}
}
위 코드에서 stop 필드가 false 일 경우에는 while문의 조건식이 true 가 되어 반복 실행하지만, stop 필드가 true 일 경우에는 while문의 조건식이 false 가 되어 while문을 빠져나온다. 그리고 스레드가 사용한 자원을 정리하고, run() 메소드가 끝나게 됨으로써 스레드는 안전하게 종료된다.
interrupt() 메소드를 이용하는 방법
interrupt() 메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException을 발생시키는 역할을 한다. 이를 이용하면 run() 메소드를 정상 종료할 수 있다.
예를 들어 아래와 같이 ThreadA 가 ThreadB를 생성해서 start() 메소드로 ThreadB를 실행했다고 가정할 때
// ThreadA
ThreadB threadB = new ThreadB();
threadB.start();
...
threadB.interrupt();
// ThreadB
public void run() {
try{
while(true){
...
Thread.sleep(1); // 일시 정지
}
} catch(InterruptedException e){
}
// 스레드가 사용한 자원 정리
}
ThreadA가 ThreadB의 interrupt() 메소드를 실행하게 되면 ThreadB 가 sleep() 메소드로 일시 정지 상태가 될 때 ThreadB에서 InterruptedException이 발생하여 예외 처리(catch) 블록으로 이동한다. 결국 ThreadB는 while 문을 빠져나와 run() 메소드를 정상 종료하게 된다.
데몬 스레드
데몬 스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다. 주 스레드가 종료되면 데몬 스레드는 강제적으로 자동 종료되는데, 그 이유는 주 스레드의 보조 역할을 수행하므로 주 스레드가 종료되면 데몬 스레드의 존재 의미가 사라지기 때문이다. 이 점을 제외하면 데몬 스레드는 일반 스레드와 큰 차이가 없다.
데몬 스레드의 적용 예는 워드프로세서의 자동 저장, 미디어 플레이어의 동영상 및 음악 재생, 쓰레기 수집기 등이 있는데, 이 기능들은 주 스레드(워드 프로세서, 미디어 플레이어, JVM)가 종료되면 같이 종료된다.
스레드를 데몬으로 만들기 위해서는 주 스레드가 데몬이 될 스레드의 setDaemon(true)를 호출해주면 된다. 아래 코드를 보면 메인 스레드가 주 스레드가 되고, AutoSaveThread가 데몬 스레드가 된다.
public static void main(String[] args){
AutoSaveThread thread = new AutoSaveThread();
thread.setDaemon(true);
thread.start();
...
}
주의할 점은 start() 메소드가 호출되고 나서 setDaemon(true)를 호출하면 IllegalThreadStateException이 발생하기 때문에 start() 메소드 호출 전에 setDaemon(true)를 호출해야 한다는 것이다.
현재 실행 중인 스레드가 데몬 스레드인지 아닌지를 구별하려면 isDeamon() 메소드의 리턴값을 조사해보면 된다. 데몬 스레드의 경우 ture를 리턴한다.
'Java' 카테고리의 다른 글
| 자바 LIFO와 FIFO 컬렉션 (0) | 2023.06.29 |
|---|---|
| 자바 컬렉션 프레임워크 (0) | 2023.06.29 |
| 자바 멀티 스레드 (0) | 2023.06.28 |
| 자바 java.util 패키지 (1) | 2023.06.28 |
| 자바 java.lang 패키지 (0) | 2023.06.28 |