스레드_스레드 제어
1. 스레드의 상태
스레드를 생성하고 실행할 때 스레드는 다양한 상태를 가지게 된다.
스래드 객체를 생성하고 start메소드를 호출하면 바로 스레드가 실행되는 것 처럼 보일 수 있다.
하지만 즉시 실행되는 것은 아니며, 스레드는 실행 대기 상태가 된다.
그리고 운영제체는 실행 대기 상태인 스레드 중 하나의 스레드를 선택하고 CPU가 run메소드를 실행하도록 한다.
실행 상태의 스레드는 run메소드의 작업 실행 코드를 모두 실행하기 전에 다시 실행 대기 상태로 돌아갈 수 있다.
이럴 경우 실행 대기 상태의 다른 스레드가 선택되어 실행 상태가 된다.
이처럼 각 스레드는 실행 대기 상태와 실행 상태를 번갈아가면서 run메소드의 작업 실행 코드를 일부분씩 실행하게 된다.
실행 상태에서 run메소드의 작업 실행 코드를 모두 실행하게 되면, 해당 스레드는 더 이상 실행할 코드가 없기 때문에 실행을 종료하게 된다. 이 상태를 종료 상태라고 한다.
스레드는 실행 과정 중 일시 정지 상태로 가기도 한다. 일시 정지 상태는 스레드를 실행할 수 없는 상태이다.
일시 정지 상태에서는 즉시 실행 상태로 복귀할 수 없으며, 실행 대기 상태를 걸쳐 실행 상태로 복귀해야한다.
2. 스레드 상태 제어
개발자는 자신이 개발한 프로세스의 올바른 작동을 위해 스레드의 상태를 적절하게 변경해줘야 할 필요가 있다.
이처럼 실행 중인 스레드의 상태를 변경하는 것을 스레드 상태 제어라고 한다.
스레드 제어를 위해서는 스레드의 상태를 변경할 수 있는 메소드를 알고 있어야 한다.
1) 주어진 시간 동안 일시 정지
Thread클래스의 sleep메소드는 실행 상태의 스레드를 일정 시간 동안 일시 정지 상태로 변경한다.
sleep메소드를 호출한 스레드는 설정한 시간 동안 일시 정지 상태가 되며, 그 후 실행 대기 상태로 변경된다.
일시 정지 상태로 설정된 시간 중에 interrput메소드가 호출되면 InterruptedException예외가 발생한다.
때문에 해당 예외에 대한 처리가 요구된다.
- try { Thread.sleep(밀리세컨드); } catch(InterruptedException e) { 예외 처리 코드 }
아래의 예제 코드는 sleep메소드 사용의 예를 보여준다.
예제 코드에서 메인 스레드는 3초 주기로 비프음을 10번 발생 시키는 작업을 실행하고자 한다.
3초 주기를 설정해주기 위해 반복문 내부에서 Thread.sleep(3000);으로 메인 스레드를 3초간 일시 정지 상태로 변경한다.
그리고 InterruptedException예외에 대비 하기 위해 try-catch 블록을 사용한다.
package sec02.exam01;
import java.awt.Toolkit;
public class SleepExample {
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i = 0; i < 10; i++) {
toolkit.beep();
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
}
}
}
}
2) 스레드의 안전한 종료
스레드는 run메소드가 모두 실행되면 자동적으로 종료 상태가 된다.
하지만 경우에 따라서는 run메소드가 모두 실행되기 전에 스레드를 종료해야 할 수 있다.
스레드를 안전하게 종료할 수 있는 두 가지 방법을 살펴보자.
*stop 플래그 이용
스레드는 run메소드가 종료되면 자동적으로 종료되므로 run메소드가 정상적으로 종료될 수 있도록 유도하는 것이 중요하다.
그 방법 중 하나는 stop 플래그를 이용하는 것이다.
아래의 예제 코드는 스레드 종료를 위한 stop 플래그의 사용을 보여준다.
PrintThread1클래스는 작업 스레드 클래스이다.
stop 플래그로 사용하기 위한 boolean 타입의 stop필드가 선언되어있다.
그리고 stop변수의 값을 true로 변경하기 위한 setStop메소드가 선언되어 있다.
run메소드 내부를 살펴보자.
while문이 작업 실행 코드를 감싸고 있다.
while문은 stop필드의 값이 true가 될 때 종료되도록 작성되어있다.
이제 메인 스레드가 있는 StopFlagExample클래스를 살펴보자.
PrintThread1 작업 스레드 객체를 참조하는 printThread변수가 선언되어 있다.
그리고 해당 작업 스레드를 실행한다.
이 상태에서 작업 스레드는 run메소드는 while문을 통해 계속해서 실행되고 있다.
해당 개발자는 작업 스레드를 1초 후에 종료하고 싶다고 가정하자.
이것을 위해 sleep메소드로 메인 스레드를 1초간 일시 정지 상태로 변경한다.
이 상태에서도 작업 스레드는 계속 실행되고 있음을 염두하자.
그리고 1초가 지난 후 메인 메소드는 계속해서 코드를 읽는다.
메인 스레드가 읽게 되는 코드는 작업 스레드의 stop필드 값을 true로 변경해주는 setStop메소드 호출이다.
이 코드를 통해 작업 스레드의 stop값을 true로 변경하여 작업 스레드 run메소드의 while문이 종료되고, 그에 따라 작업 스레드도 종료된다.
package sec02.exam02;
public class PrintThread1 extends Thread {
private boolean stop;
public void setStop(boolean stop) {
this.stop = stop;
}
public void run() {
while(!stop) {
System.out.println("실행 중");
}
System.out.println("자원 정리");
System.out.println("실행 종료");
}
}
package sec02.exam02;
public class StopFlagExample {
public static void main(String[] args) {
PrintThread1 printThread = new PrintThread1();
printThread.start();
try { Thread.sleep(1000); } catch (InterruptedException e) {}
printThread.setStop(true);
}
}
*interrupt메소드 이용
Thread클래스의 interrupt메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException을 발생시키는 메소드이다.
이것을 통해 run메소드를 정상적으로 종료시킬 수 있다.
주의할 점은 해당 스레드가 일시 정지 상태에 있을 때 InterruptedException을 발생시킨다는 것이다.
스레드는 일시 정지 상태일 때 interrupt메소드 호출을 알 수 있다.
아래의 예제 코드는 interrupt메소드 이용의 예를 보여준다.
PrintThread2는 작업 스레드 클래스이다.
run메소드 내부의 작업 실행 코드를 살펴보자.
InterruptedException을 처리하기 위한 try-catch 블록이 있으며, 해당 블록 내부에 while문이 작성되어 있다.
그리고 while문 내부에는 해당 스레드를 0.001초간 일시 정지 상태로 변경하기 위한 sleep메소드가 작성되어 있다.
이제 메인 스레드가 있는 InterruptExample클래스를 살펴보자.
PrintThread2작업 스레드 객체를 참조하는 thread변수가 선언되어 있다.
그리고 PrintThread2작업 스레드를 실행한다.
이러면 PrintThread2작업 스레드의 run메소드에 작성된 while문이 계속해서 실행된다.
개발자는 while문을 1초간 실행하고 종료하고 싶다.
그것을 위해 메인 스레드를 1초간 일시 정지 상태로 만드는 sleep메소드를 호출한다.
1초 후 thread.interrupt();를 통해 PrintThread2작업 스레드에 InterruptedException을 발생시킨다.
주의할 점은 interrupt메소드는 InterruptedException을 즉시 발생 시키기 않고, 해당 스레드가 일시 정지 상태일 때 발생시킨다는 것이다.
while문의 Thread.sleep(1);을 통해 PrintThread2작업 스레드가 일시 정지 상태가 될 때 InterruptedException이 발생되고 while문은 종료된다.
그 후 run메소드의 나머지 작업 실행 코드가 실행된 후 작업 스레드는 종료된다.
package sec02.exam03;
public class PrintThread2 extends Thread {
public void run() {
try {
while(true) {
System.out.println("실행 중");
Thread.sleep(1);
}
} catch(InterruptedException e) {}
System.out.println("자원 정리");
System.out.println("실행 정리");
}
}
package sec02.exam03;
public class InterruptExample {
public static void main(String[] args) {
Thread thread = new PrintThread2();
thread.start();
try { Thread.sleep(1000); } catch(InterruptedException e) {}
thread.interrupt();
}
}
스레드를 일시 정지 상태로 만들지 않고도 interrupt메소드 호출 여부를 알 수 있는 방법이 있다.
Thread클래스의 정적 메소드인 interrupted메소드, 인스턴스 메소드인 isInterrupted메소드는 interrupt메소드가 호출되면 true를 반환한다.
이것을 통해 스레드를 일시 정지 상태로 만들지 않고 interrupt메소드 호출 여부를 알 수 있다.
아래의 예제 코드는 PrintThread2작업 스레드 클래스의 내부 코드를 interrupted메소드 사용의 예를 보여주기 위해 수정한 것이다.
while문을 살펴보자.
해당 스레드를 일시 정지 상태로 만들기 위한 Thread.sleep();은 삭제 되었다.
그 대신 if(Thread.interrupted()) { break; }를 통해 interrupt메소드 호출 여부를 확인하고 interrupt메소드가 호출 되었다면 while문을 종료한다.
package sec02.exam04;
public class PrintThread2 {
public void run() {
while(true) {
System.out.println("실행 중");
if(Thread.interrupted()) {
break;
}
}
System.out.println("자원 정리");
System.out.println("실행 종료");
}
}
출처: 혼자 공부하는 자바(신용권)