특정 스레드를 작업 중간에 중단하기


- 특정 스레드의 작업을 중단하는 가장 쉬운 방법은 변수를 사용하는 것이다.
- 여기서는 runFlag 를 사용해서 "work"스레드에 작업 중단을 지시할 수 있다.
- 작업 하나에 3초가 걸린다고 가정하고, sleep(3000)을 사용하자.
- "main" 스레드는 4초 뒤에 작업 중단을 지시한다.
- "volatile" 키워드는 단순히 여러 스레드에서 공유하는 값에 사용하는 키워드 라고 알아두자.

- "work" 스레드는 runFlag가 true인 동안 계속 실행된다.

- 프로그램 시작 후 4초 뒤에 "main" 스레드는 runFlag를 false로 변경한다.
- "work" 스레드는 while(runFlag) 코드에서 runFlag의 조건이 false로 변한 것을 확인하고, while문을 빠져나가면서 작업을 종료한다.
문제점
실행을 해보면 알겠지만 "main" 스레드가 runFlag=false를 통해 작업 중단을 지시해도, "work" 스레드가 즉각 반
응하지 않는다. 로그를 보면 작업 중단 지시 2초 정도 이후에 자원을 정리하고 작업을 종료한다
문제가 되는 코드
while (runFlag) {
log("작업 중");
sleep(3000);
}
- "main"스레드가 runFlag를 false로 변경해도, "work" 스레드는 sleep(3000)을 통해 3초간 잠들어 있다. 3초간의 잠이 깬 다음에 while(runFlag)코드를 실행해야, runFlag를 확인하고 작업을 중단할 수 있다.
- 참고로 runFlag를 변경한 후 2초라는 시간이 지난 이후에 작업이 종료되는 이유는 "work" 스레드가 3초에 한 번씩 깨어나서 runFlag를 확인하는데, "main" 스레드가 4초에 runFlag를 변경했기 때문이다.
- "work" 스레드 입장에서 보면 두 번째 sleep()에 들어가고 1초 후 "main" 스레드가 runFlag를 변경한다. 3초간 sleep() 이므로 아직 2초가 더 있어야 깨어난다.
인터럽트
예를 들어서, 특정 스레드가 Thread.sleep()을 통해 쉬고 있는데, 처리해야 하는 작업이 들어와서 해당 스레드를 급하게 깨워야 할 수 있다. 또는 sleep()으로 쉬고 있는 스레드에게 더는 일이 없으니, 작업 종료를 지시할 수도 있다. 인터럽트를 사용하면, "WAITING" , "TIMED_WAITING"
같은 대기 상태의 스레드를 직접 깨워서, 작동하는 "RUNNABLE" 상태로 만들 수 있다.


- 예제의 run() 에서는 인터럽트를 이해하기 위해, 직접 만든 sleep() 대신에 Thread.sleep() 를 사용하고, try ~ catch 도 사용하자.
- 특정 스레드의 인스턴스에 interrupt() 메서드를 호출하면, 해당 스레드에 인터럽트가 발생한다. 인터럽트가 발생하면 해당 스레드에 "InterruptedException"이 발생한다.
- 이때 인터럽트를 받은 스레드는 대기 상태에서 깨어나 "RUNNABLE" 상태가 되고, 코드를 정상 수행한다.
- 이때 "InterruptedException"을 catch 로 잡아서 정상 흐름으로 변경하면 된다.
- 참고로 interrupt() 를 호출했다고 해서 즉각 "InterruptedException" 이 발생하는 것은 아니다. 오직 sleep() 처럼 "InterruptedException"을 던지는 메서드를 호출 하거나 또는 호출 중일 때 예외가 발생한다.
- 예를 들어서 위 코드에서 while(true) , log("작업 중") 에서는 "InterruptedException"이 발생하지 않는다.
- Thread.sleep() 처럼 "InterruptedException"을 던지는 메서드를 호출하거나 또는 호출하며 대기중일 때 예외가 발생한다.
- thread.interrupt() 를 통해 작업 중단을 지시를 하고, 거의 즉각적으로 인터럽트가 발생한 것을 확인할 수 있다.
- 이때 work 스레드는 "TIMED_WAITNG" -> "RUNNABLE" 상태로 변경되면서 "InterruptedException" 예외가 발생한다.
- 참고로 스레드가 "RUNNABLE" 상태여야 catch 의 예외 코드도 실행될 수 있다.
- 실행 결과를 보면 work 스레드가 catch 블럭 안에서 "RUNNABLE" 상태로 바뀐 것을 확인할 수 있다

- main 스레드가 4초 뒤에 work 스레드에 interrupt() 를 건다.
- work 스레드는 인터럽트 상태(true)가 된다.
- 스레드가 인터럽트 상태일 때는, sleep()처럼 "InterruptedException"이 발생하는 메서드를 호출하거나 또는 이미 호출하고 대기 중이라면 "InterruptedException"이 발생한다.
- 이때 2가지 일이 발생한다.
- work 스레드는 "TIMED_WAITING" 상태에서 "RUNNABLE" 상태로 변경되고, "InterruptedException" 예외를 처리하면서 반복문을 탈출한다.
- work 스레드는 인터럽트 상태가 되었고, 인터럽트 상태이기 때문에 인터럽트 예외가 발생한다.
- 인터럽트 상태에서 인터럽트 예외가 발생하면 work 스레드는 다시 작동하는 상태가 된다. 따라서 work 스레드의 인터럽트 상태는 종료된다.
- work 스레드의 인터럽트 상태는 false 로 변경된다.
- 인터럽트가 적용되고, 인터럽트 예외가 발생하면, 해당 스레드는 실행 가능 상태가 되고, 인터럽트 발생 상태도 정상으로 돌아온다.
- 인터럽트를 사용하면 대기중인 스레드를 바로 깨워서 실행 가능한 상태로 바꿀 수 있다. 덕분에 단순히 runFlag 를 사용하는 이전 방식보다 반응성이 좋아진 것을 확인할 수 있다.
인터럽트 여부 체크
while (true) {
//인터럽트 체크 안함 log("작업 중");
Thread.sleep(3000); //여기서만 인터럽트 발생
}
- 기존 코드에서는 인터럽트가 발생해도 이 부분은 항상 while문은 true 이기 때문에 다음 코드로 넘어간다. 그리고 sleep()을 호출하고 나서야 인터럽트가 발생하는 것이다
- 인터럽트 상태를 확인하면 while문을 체크하는 부분에서 더 빠르게 while문을 빠져나갈 수 있다.
- 추가로 인터럽트의 상태를 직접 확인하면, 다음과 같이 인터럽트를 발생시키는 sleep()과 같은 코드가 없어도 인터럽트 상태를 직접 확인하기 때문에 while문을 빠져나갈 수 있다


- Thread.currentThread()로 이 코드를 실행하는 스레드를 조회할 수 있다.
- isInterrupted()를 사용하면 스레드가 인터럽트 상태인지 확인할 수 있다.
- 반복문으로 작업 중을 계속 출력하기 때문에 상당히 많은 작업 중 로그가 출력된다.
- "main" 스레드는 interrupt() 메서드를 사용해서, "work"스레드에 인터럽트를 건다.
- "work"스레드는 인터럽트 상태이다. isInterrupted()=true 가 된다.
- 이때 다음과 같이 while 조건이 false 가 되면서 while문을 탈출한다.
- while (! Thread.currentThread(). isInterrupted())
- while (!true)
- while (false)
여기까지 보면 아무런 문제가 없어 보인다. 하지만 이 코드에는 심각한 문제가 있다. 바로 work 스레드의 인터럽트 상태가 true로 계속 유지된다는 점이다. 앞서 인터럽트 예외가 터진 경우 스레드의 인터럽트 상태는 "false"가 된다. 반면에 isInterrupted() 메서드는 인터럽트의 상태를 변경하지 않는다. 단순히 인터럽트의 상태를 확인만 한다.
work 스레드는 이후에 자원을 정리하는 코드를 실행하는데, 이때도 인터럽트의 상태는 계속 "true"로 유지된다. 이때 만약 인터럽트가 발생하는 sleep()과 같은 코드를 수행한다면, 해당 코드에서 인터럽트 예외가 발생하게 된다. 이것은 우리가 기대한 결과가 아니다! 우리가 기대하는 것은 while() 문을 탈출하기 위해 딱 한 번만 인터럽트를 사용하는 것이지, 다른 곳에서도 계속해서 인터럽트가 발생하는 것이 아니다.
결과적으로 자원 정리를 하는 도중에 인터럽트가 발생해서, 자원 정리에 실패한다.
자바에서 인터럽트 예외가 한 번 발생하면, 스레드의 인터럽트 상태를 다시 정상( false )으로 돌리는 것은 이런 이유 때문이다.
스레드의 인터럽트 상태를 정상으로 돌리지 않으면 이후에도 계속 인터럽트가 발생하게 된다.
인터럽트의 목적을 달성하면 인터럽트 상태를 다시 정상으로 돌려두어야 한다.
참고로 이 예제에서 자원 정리에 실패할 때 인터럽트 예외가 발생하면서 인터럽트의 상태가 정상( false )으로 돌아온다.
work 스레드 인터럽트 상태 3 = false
-> while(인터럽트_상태_확인) 같은 곳에서 인터럽트의 상태를 확인한 다음에, 만약 인터럽트 상태( true )라면 인터럽트 상태를 다시 정상( false )으로 돌려두면 될 것이다.
인터럽트 여부 체크 후, Runnable로 변경
Thread.interrupted()
- 스레드의 인터럽트 상태를 단순히 확인만 하는 용도라면 isInterrupted()를 사용하면 된다. 하지만 직접 체크해서 사용할 때는 Thread.interrupted()를 사용해야 한다. 이 메서드는 다음과 같이 작동한다.
- 스레드가 인터럽트 상태라면 true를 반환하고, 해당 스레드의 인터럽트 상태를 false로 변경한다.
- 스레드가 인터럽트 상태가 아니라면false를 반환하고, 해당 스레드의 인터럽트 상태를 변경하지 않는다


- "main" 스레드는 interrupt() 메서드를 사용해서, "work" 스레드에 인터럽트를 건다.
- "work" 스레드는 인터럽트 상태이다. Thread.interrupted()의 결과는 true 가 된다.
- Thread.interrupted()는 이때 work 스레드의 인터럽트 상태를 정상( false )으로 변경한다.
- 이때 다음과 같이 while 조건이 false 가 되면서 while문을 탈출한다.
- while(! Thread.interrupted())
- while(! true)
- while(false)
- Thread.interrupted()를 호출했을 때 스레드가 인터럽트 상태( true )라면, true를 반환하고, 해당 스레드의 인터럽트 상태를 false로 변경한다
- 결과적으로 while문을 탈출하는 시점에, 스레드의 인터럽트 상태도 false로 변경된다.
- "work" 스레드는 이후에 자원을 정리하는 코드를 실행하는데, 이때 인터럽트의 상태는 false 이므로 인터럽트가 발생하는 sleep()과 같은 코드를 수행해도 인터럽트가 발생하지 않는다. 이후에 자원을 정상적으로 잘 정리하는 것을 확인할 수 있다.
- 인터럽트의 상태를 직접 체크해서 사용하는 경우 Thread.interrupted()를 사용하면 이런 부분이 해결된다. 참고로 isInterrupted()는 특정 스레드의 상태를 변경하지 않고 확인할 때 사용한다.
- 물론 꼭 이것만이 정답은 아니다. 예를 들어 너무 긴급한 상황이어서 자원 정리도 하지 않고, 최대한 빨리 스레드를 종료 해야 한다면 해당 스레드를 다시 인터럽트 상태로 변경하는 것도 방법이다.
자바는 인터럽트 예외가 한 번 발생하면, 스레드의 인터럽트 상태를 다시 정상( false )으로 돌린다.
스레드의 인터럽트 상태를 정상으로 돌리지 않으면 이후에도 계속 인터럽트가 발생하게 된다.
인터럽트의 목적을 달성하면 인터럽트 상태를 다시 정상으로 돌려두어야 한다
'멀티스레드와 동시성' 카테고리의 다른 글
| Ch04. 스레드 제어와 생명 주기2 - yield (0) | 2024.08.08 |
|---|---|
| Ch04. 스레드 제어와 생명 주기2 - 프린트 예제 (0) | 2024.08.07 |
| Ch03. 스레드 제어와 생명 주기 - Join (0) | 2024.08.04 |
| Ch03. 스레드 제어와 생명 주기 - 체크 예외 재정의 (0) | 2024.08.02 |
| Ch03. 스레드 제어와 생명 주기 - 설명 / 코드 (0) | 2024.08.02 |