스레드의 작업을 중간에 중단하는 방법
- flag값 설정 및 수정
- interrupt() 사용
flag값 설정
특정 스레드의 실행을 flag 값이 true인 동안 계속해서 작업하도록 설정한다
특정 스레드를 중단하는 시점에 flag 값을 false로 바꾼다
이렇게 하면, 다음 반복문 실행 시점에 flag 값을 확인하고 해당 작업을 빠져 나온다
static class MyTask implements Runnable {
volatile boolean runFlag = true;
@Override
public void run() {
while (runFlag) {
log("작업 중");
sleep(3000);
}
log("자원 정리");
log("작업 종료");
}
}
runFlag 값을 true로 설정하고, 작업을 진행하는 스레드
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task, "work");
thread.start();
sleep(4000);
log("작업 중단 지시 runFlag=false");
task.runFlag = false;
}
Mytask를 실행하고, 4초 후에 해당 스레드의 상태를 false로 수정한다
다음과 같은 방식을 활용하면 간단히 스레드의 작업을 중단 시킬 수 있다.
하지만, flag가 false로 바뀌는 시점에 그 시점의 반복문에서 실행하고 있는 코드는 모두 수행하고,
다음 조건 체크 시 빠져 나오게 된다.
interrupt()
인터럽트는 WATING, TIMED_WAITING과 같은 대기 상태의 스레드를 깨우고, RUNNABLE 상태로 만들 수 있다.
인터럽트 관련 메소드
- thread.interrupt() - 해당 스레드에게 인터럽트를 건다
- Thread.currentThread().isInterrupted() - 해당 스레드의 상태가 인터럽트인지 체크( 인터럽트 상태 변경 x )
- Thread.interrupted() - 스레드의 상태가 인터럽트가 걸렸으면 true를 반환하고, 인터럽트의 상태를 변경해준다( true > false )
상당히 재미있다
interrupt()는 해당 스레드에게 인터럽트를 걸어준다
인터럽트를 건다고 하더라도, 즉각적으로 반응하지는 않는다.
즉, 해당 스레드에게 인터럽트가 걸리는 상황이 발생할때( 예시에서는 Sleep() ) 인터럽트가 발생한다.
인터럽트가 걸리면 InterruptedException이 발생하게되고, 인터럽트 상태가 해제된다
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task, "work");
thread.start();
sleep(4000);
log("작업 중단 지시 thread.interrupt()");
thread.interrupt();
log("work 스레드 인터럽트 상태1 = " + thread.isInterrupted()) ;
}
thread.interrupt()를 통해서 thread 에게 인터럽트를 걸어준다.
static class MyTask implements Runnable {
volatile boolean runFlag = true;
@Override
public void run() {
try {
while (true) {
log("작업 중");
Thread.sleep(3000);
}
} catch (InterruptedException e) {
log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
log("interrupt message = " + e.getMessage());
log("state = " + Thread.currentThread().getState());
}
log("자원 정리");
log("작업 종료");
}
}
해당 스레드는 작업을 이어가다가 sleep(3000) 을 만나면, 인터럽트가 걸린다(걸리면서 해제된다)
isInterrupted()는 현재 스레드의 인터럽트 상태만을 체크한다.
즉, 인터럽트가 걸렸다고해서 해제되는 상황이 발생하지 않는다. 그냥 체크만 한다.
작업중 interrupt()가 요청되고 main에서 바로 확인한 현재 인터럽트 상태는 true가 반환된다
그 후에 곧바로 sleep 상태에 있는 스레드에게 인터럽트가 걸리게 되고 InterruptedException을 만나면서
인터럽트 상태는 false가 된다
( sleep 상태가 아니고 처리하는 로직이 있었으면, 로직을 수행한다 )
interrupted()는 현재 스레드의 상태가 인터럽트 상태라면, true 반환 후에 인터럽트 상태를 변경해준다( true > false )
static class MyTask implements Runnable {
@Override
public void run() {
while (!Thread.interrupted()) { // 인터럽트 상태 변경X
log("작업 중");
}
log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
try {
log("자원 정리");
Thread.sleep(500);
log("작업 종료");
} catch (InterruptedException e) {
log("자원 정리 실패 - 자원 정리 중 인터럽트 발생 ");
log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
}
}
}
while문의 조건을 수정하였다
기존에는 true로 주어서 무한반복이 되게끔하고, sleep 상태일때 인터럽트에 걸리면서 빠져나왔다면,
현재는 Thread.interrupted() 메소드를 통해서 인터럽트가 걸리게 되면 바로 해당 반복문을 빠져나온다.
interrupted() 메소드는 인터럽트 상태일때, true를 반환하고 그 상태값을 변경해준다
interrupt()를 요청한 직후 main에서 확인한 인터럽트 상태는 true이다
곧바로 스레드의 Thread.interrupted()가 상태값을 변경하면서 반복문을 빠져나오고 상태는 false가 된다;
상태값을 변경했기 때문에, 자원정리중 sleep()이 발생하여도 InterruptException 에 걸리지 않는다
만약, Thread.interrupted()를 사용하지 않고 Thread.currentThread.isInterrupted() 메소드를 사용하여 반복문을 빠져나오면
반복문을 빠져나오며 상태값을 변경하지 않기때문에, 자원정리의 sleep에서 인터럽트가 발생하여 Exception이 발생하게 된다.
어떤것이 무조건 좋다고 할 순 없다
상황에 맞게 설정해야할거 같다
interrupt() / isInterrupted() / interrupted() 의 역할이 무엇인지 확실하게 기억하고 넘어가자
이름이...살짝 헷갈리긴 하다...ㅎㅎ
interrupted()와 isInterrupted()
한가지 궁금점이 생겼다
isInterrupted()는 Thread.currentThread에서 호출하는데, interrupted()는 Thread에서 호출하는 점이다
그럼 혹시 다른스레드에서 interrupt를 호출해도 Thread.interrupted()에서 걸리게 되는걸까?
public class ThreadTest {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task, "work");
MyTask2 task2 = new MyTask2();
Thread thread2 = new Thread(task2, "work2");
thread.start();
thread2.start();
sleep(500);
log("작업 중단 지시 thread2.interrupt()");
thread2.interrupt();
log("work 스레드 인터럽트 상태1 = " + thread.isInterrupted()) ;
log("work 스레드2 인터럽트 상태1 = " + thread2.isInterrupted()) ;
}
static class MyTask implements Runnable {
@Override
public void run() {
while (!Thread.interrupted()) { // 인터럽트 상태 변경X
log("작업 중");
sleep(2000);
}
log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
try {
log("자원 정리");
Thread.sleep(2000);
log("작업 종료");
} catch (InterruptedException e) {
log("자원 정리 실패 - 자원 정리 중 인터럽트 발생 ");
log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
}
}
}
static class MyTask2 implements Runnable {
volatile boolean runFlag = true;
@Override
public void run() {
try {
while (runFlag) {
log("작업 중");
Thread.sleep(2000);
}
} catch (InterruptedException e) {
log("work2 인터럽트!");
}
log("자원 정리");
log("작업 종료");
}
}
}
( 테스트용으로 한번에 코드를 올려보았다 )
각각 다른 스레드를 생성한다(MyTask, MyTask2)
MyTask는 interrupted()를 사용하여 인터럽트가 걸리면 바로 빠져 나오도록 설정하고
MyTask2는 sleep 상태일때 인터럽트가 걸리도록 설정해두었다
main에서는 thread2(MyTask2)에게 인터럽트를 걸었다
작업의 결과를 보면, 정상적으로 thread2에게 인터럽트가 걸렸다
sleep 상태의 thread2는 인터럽트에 걸려 작업이 종료된다
반면, 인터럽트 요청이 없는 thread1은 작업이 끝나지 않는다
Thread.currentThread가 아니라, Thread에서 걸어주어도 자신의 스레드만 체크한다
메소드를 확인해보면,
interrupted()는 안에서 currentThread()를 실행하고 있다.
테스트 해보길 잘했다
yield
- 특정한 스레드가 바쁘지 않은 상황에 다른 스레드에게 CPU 실행기회를 양보해 주는 스레드의 메소드
- yield 메소드를 호출한 스레드는 RUNNABLE 상태를 유지하면서 CPU를 양보한다
- 짧은 sleep()으로도 상태를 바꿀 수 있지만, TIME_WAITING으로 상태를 변경하고, 짧은 시간 스레드가 멈춘다
- 양보할 스레드가 없으면, 나의 스레드가 실행된다
- 최근엔, 많은 코어를 가진 CPU들이 많아서 양보가 큰 의미가 없을 수 있다
'Java > 김영한의 실전자바' 카테고리의 다른 글
스레드 - join() (0) | 2024.08.26 |
---|---|
스레드 제어 (0) | 2024.08.22 |
스레드의 생성과 실행 (0) | 2024.08.15 |
프로세스와 스레드 (0) | 2024.08.13 |
[김영한의 실전 자바 - 기본편] 기본형과 참조형 (0) | 2024.01.21 |