오늘 Thread 클래스의 isInterrupted 메소드가 스레드의 인터럽트 상태를 얻을 수 있다는 것을 보았습니다.
그래서 이를 확인하기 위한 예제를 작성했습니다.
public class Interrupt { public static void main(String[] args) throws Exception { Thread t = new Thread(new Worker()); t.start(); Thread.sleep(200); t.interrupt(); System.out.println("Main thread stopped."); } public static class Worker implements Runnable { public void run() { System.out.println("Worker started."); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("Worker IsInterrupted: " + Thread.currentThread().isInterrupted()); } System.out.println("Worker stopped."); } } }
내용은 매우 간단합니다. 메인 스레드 메인은 하위 스레드 작업자를 시작한 다음 작업자를 500ms 동안 휴면 상태로 두고, 메인 스레드는 200ms 동안 휴면 상태를 유지합니다. 그 후 메인은 작업자 스레드의 인터럽트 메서드를 호출합니다. 작업자를 중단하려면 작업자가 중단된 후 중단 상태가 인쇄됩니다. 실행 결과는 다음과 같습니다.
Worker started. Main thread stopped. Worker IsInterrupted: falseWorker stopped.
Worker가 분명히 중단되었지만 isInterrupted() 메서드가 실제로 false를 반환한 이유는 무엇입니까?
Stackoverflow를 검색한 결과 일부 네티즌들이 InterruptedException을 발생시키는 메소드의 JavaDoc(또는 소스 코드)을 볼 수 있다고 언급한 것을 발견하여 Thread.sleep 메소드의 문서를 확인했습니다. 문서에 설명된 내용:
InterruptedException - 스레드가 현재 스레드를 중단한 경우 이 예외가 발생하면 현재 스레드의 중단된 상태가 지워집니다.
다음 문장은 "이 예외가 발생하면 인터럽트 상태가 지워진 것입니다." 따라서 isInterrupted() 메서드는 false를 반환해야 합니다. 하지만 때로는 true를 반환하기 위해 isInterrupted 메서드가 필요할 수도 있습니다. 여기에서는 먼저 인터럽트, 인터럽트 및 isInterrupted의 차이점에 대해 설명합니다.
interrupt 메소드는 스레드를 인터럽트하는 데 사용되며 이 메소드를 호출하는 스레드의 상태는 "interrupted" 상태로 설정됩니다. 참고: 스레드 인터럽트는 스레드의 인터럽트 상태 비트만 설정하고 스레드를 중지하지 않습니다. 사용자는 스레드의 상태를 모니터링하고 스스로 처리해야 합니다. 스레드 중단을 지원하는 메서드(즉, 여기서 sleep과 같이 스레드가 중단된 후 InterruptedException을 발생시키는 메서드와 Object.wait 및 기타 메서드)는 일단 스레드의 중단 상태를 모니터링하는 것입니다. 스레드가 "interrupted status"로 설정되면 인터럽트 예외가 발생합니다. 이 보기는 다음 기사에서 확인할 수 있습니다.
interrupt()는 단지 스레드의 중단 상태를 설정합니다. 중단된 스레드에서 실행 중인 코드는 나중에 중단된 상태를 폴링하여 수행 중인 작업을 중지하도록 요청되었는지 확인할 수 있습니다.
interrupted 메소드의 구현을 살펴보겠습니다:
public static boolean interrupted() { return currentThread().isInterrupted(true); }
및 isInterrupted의 구현:
public boolean isInterrupted() { return isInterrupted(false); }
이 두 메소드 중 하나는 정적이며 다른 하나는 그렇지 않지만 실제로는 중단된 메서드에 의해 전달된 매개 변수가 true이고 inInterrupted에 의해 전달된 매개 변수가 false라는 점을 제외하면 모두 동일한 메서드를 호출하고 있습니다. 그렇다면 이 매개변수는 무엇을 의미하는가? isInterrupted(boolean) 메소드의 구현을 살펴보겠습니다.
/** * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. */private native boolean isInterrupted(boolean ClearInterrupted);
이것은 원시 메소드입니다. 매개변수 이름 ClearInterrupted가 표시되지 않아도 상관없습니다. 이 매개변수의 역할을 명확하게 표현했습니다.---인터럽트 상태를 지울지 여부입니다. 메소드의 주석에도 "전달된 ClearInterrupted 매개변수 값에 따라 인터럽트 상태가 재설정됩니다."라고 명확하게 표시되어 있습니다. 따라서 Interrupted 정적 메소드는 인터럽트 상태를 삭제하지만(전달된 매개변수 ClearInterrupted는 true), 인스턴스 메소드 isInterrupted는 그렇지 않습니다(전달된 매개변수 ClearInterrupted는 false입니다).
방금 질문으로 돌아가서: isInterrupted 메서드가 true를 반환하도록 하려면 isInterrupted 메서드를 호출하기 전에 Interrupt() 메서드를 다시 호출하여 중단된 상태를 복원할 수 있습니다.
public class Interrupt { public static void main(String[] args) throws Exception { Thread t = new Thread(new Worker()); t.start(); Thread.sleep(200); t.interrupt(); System.out.println("Main thread stopped."); } public static class Worker implements Runnable { public void run() { System.out.println("Worker started."); try { Thread.sleep(500); } catch (InterruptedException e) { Thread curr = Thread.currentThread(); //再次调用interrupt方法中断自己,将中断状态设置为“中断” curr.interrupt(); System.out.println("Worker IsInterrupted: " + curr.isInterrupted()); System.out.println("Worker IsInterrupted: " + curr.isInterrupted()); System.out.println("Static Call: " + Thread.interrupted());//clear status System.out.println("---------After Interrupt Status Cleared----------"); System.out.println("Static Call: " + Thread.interrupted()); System.out.println("Worker IsInterrupted: " + curr.isInterrupted()); System.out.println("Worker IsInterrupted: " + curr.isInterrupted()); } System.out.println("Worker stopped."); } } }
실행 결과:
Worker started. Main thread stopped. Worker IsInterrupted: true Worker IsInterrupted: true Static Call: true ---------After Interrupt Status Cleared---------- Static Call: false Worker IsInterrupted: false Worker IsInterrupted: false Worker stopped.
실행 결과에서도 isInterrupted 메소드가 실행된 것을 확인할 수 있습니다. 처음 두 번 호출됨 둘 다 true를 반환합니다. 이는 isInterrupted 메서드가 스레드의 인터럽트 상태를 변경하지 않음을 나타냅니다. 그런 다음 정적 Interrupted() 메서드를 호출합니다. 처음으로 true를 반환하면 스레드가 중단되었음을 의미합니다. 두 번째에는 false를 반환합니다. 왜냐하면 첫 번째 호출 시 인터럽트 상태가 지워졌기 때문입니다. isInterrupted() 메서드에 대한 마지막 두 호출은 확실히 false를 반환합니다.
그렇다면 어떤 시나리오에서 catch 블록의 스레드를 중단(인터럽트 상태 재설정)해야 합니까?
대답은 다음과 같습니다. InterruptedException을 발생시킬 수 없는 경우(여기서 Thread.sleep 문이 Runnable의 run 메소드에 배치된 것처럼 이 메소드는 확인된 예외가 발생하는 것을 허용하지 않습니다) 상위 계층 호출자에게 인터럽트가 발생하면 인터럽트 상태는 캐치에서만 재설정될 수 있습니다.
InterruptedException을 포착했지만 다시 발생시킬 수 없는 경우 호출 스택의 상위 코드가 중단을 학습하고 원하는 경우 이에 응답할 수 있도록 중단이 발생했다는 증거를 보존해야 합니다. Listing 3에 표시된 대로 Interrupt()를 호출하여 현재 스레드를 "재인터럽트"합니다.
Listing 3: InterruptedException을 포착한 후 중단된 상태 복원
public class TaskRunner implements Runnable { private BlockingQueue<Task> queue; public TaskRunner(BlockingQueue<Task> queue) { this.queue = queue; } public void run() { try { while (true) { Task task = queue.take(10, TimeUnit.SECONDS); task.execute(); } } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); } } }
那么问题来了:为什么要在抛出InterruptedException的时候清除掉中断状态呢?
这个问题没有找到官方的解释,估计只有Java设计者们才能回答了。但这里的解释似乎比较合理:一个中断应该只被处理一次(你catch了这个InterruptedException,说明你能处理这个异常,你不希望上层调用者看到这个中断)。