从RocketMQ的一个PR说起
前几天看RocketMQ
PR发现一个有意思的内容是捕获到的线程中断异常不应该被忽略.
-
原逻辑
1 2 3 4
try { scheduledExecutorService.awaitTermination(5000, TimeUnit.MILLISECONDS); } catch (InterruptedException ignore) { }
-
新逻辑
1 2 3 4 5 6
try { scheduledExecutorService.awaitTermination(5000, TimeUnit.MILLISECONDS); } catch (InterruptedException ignore) { BrokerController.LOG.warn("shutdown ScheduledExecutorService was Interrupted! ", ignore); Thread.currentThread().interrupt(); }
- 新增了
Thread.currentThread().interrupt();
仅仅是设置了一个标记位
-
java.util.concurrent.ScheduledThreadPoolExecutor#awaitTermination
中的LockSupport.parkNanos
被唤醒之后会检查中断标记, 进而抛出异常java.lang.InterruptedException
-
Thread.interrupted()
静态方法会清除中断标记(1中设置的) - 为什么还要重新复位中断状态
- 平衡程序的响应能力与健壮性
线程中断的正确使用方式
Java线程中断的相关API
方法 | 作用 | 是否重置/设置标记位 |
---|---|---|
public void interrupt() | 设置中断 | 是 |
public boolean isInterrupted() | 判断是否中断 | 否 |
public static boolean interrupted() | 判断是否中断 | 是 |
传递异常
抛出中断异常让调用者决定该怎么处理中断, 会导致调用者不得不声明catch该异常
1
2
3
4
5
6
7
public static void propagateException() throws InterruptedException {
Thread.sleep(1000);
Thread.currentThread().interrupt();
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
恢复中断
调用者可根据需求检查中断状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class InterruptExample extends Thread {
public static Boolean restoreTheState() {
InterruptExample thread1 = new InterruptExample();
thread1.start();
thread1.interrupt();
return thread1.isInterrupted();
}
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); //set the flag back to true
}
}
}
自定义异常处理
抛出自定义业务异常 + 重置标记位的同时抛出业务异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class CustomInterruptedException extends Exception {
CustomInterruptedException(String message) {
super(message);
}
}
public static void throwCustomException() throws Exception {
Thread.sleep(1000);
Thread.currentThread().interrupt();
if (Thread.interrupted()) {
throw new CustomInterruptedException("This thread was interrupted");
}
}
//重置标记位的同时抛出异常
public static Boolean handleWithCustomException() throws CustomInterruptedException{
try {
Thread.sleep(1000);
Thread.currentThread().interrupt();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new CustomInterruptedException("This thread was interrupted...");
}
return Thread.currentThread().isInterrupted();
}
线程中断的本质
-
java.lang.Thread#interrupt
中的interrupt0()
-
找到OpenJDK的
jdk/src/share/native/java/lang/Thread.c
-
找到
hotspot/src/share/vm/prims/jvm.cpp
中的JVM_Interrupt
-
找到
hotspot/src/os/linux/vm/os_linux.cpp
中的os::interrupt
设置中断标记位然后唤醒对应线程, OrderAccess::fence()即是保证可见性的重要方法, JVM中同步/锁的底层代码均有该方法出现, 后序再解析这个相关
-
hotspot/src/share/vm/runtime/osThread.hpp
中的set_interrupted
-
-
Thread中的
isInterrupted(boolean ClearInterrupted)
-
找到OpenJDK的
jdk/src/share/native/java/lang/Thread.c
-
找到
hotspot/src/share/vm/prims/jvm.cpp
中的JVM_Interrupt
可见clear_interrupted为是否中断标记位
-
追到
hotspot/src/os/linux/vm/os_linux.cpp
中的os::is_interrupted
可见根据是否清除中断标记来执行要不要设置中断标记为false
-
参考资料
How to Handle InterruptedException in Java