Java线程中断的本质

Posted by xdshent on April 3, 2023

从RocketMQ的一个PR说起

#5515

前几天看RocketMQPR发现一个有意思的内容是捕获到的线程中断异常不应该被忽略.

  • 原逻辑

    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();
    }
    
  1. 新增了Thread.currentThread().interrupt();

    仅仅是设置了一个标记位

    thread-interrupt

  2. java.util.concurrent.ScheduledThreadPoolExecutor#awaitTermination中的LockSupport.parkNanos被唤醒之后会检查中断标记, 进而抛出异常java.lang.InterruptedException

    aqs-await-nanos

    check-interrupt-while-waiting

  3. Thread.interrupted()静态方法会清除中断标记(1中设置的) thread-static-is-interrupted

  4. 为什么还要重新复位中断状态
    • 平衡程序的响应能力与健壮性

线程中断的正确使用方式

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()

    1. 找到OpenJDK的jdk/src/share/native/java/lang/Thread.c

      openjdk-thread

    2. 找到hotspot/src/share/vm/prims/jvm.cpp中的JVM_Interrupt

      openjdk-jvm-cpp

    3. 找到hotspot/src/os/linux/vm/os_linux.cpp中的os::interrupt

      设置中断标记位然后唤醒对应线程, OrderAccess::fence()即是保证可见性的重要方法, JVM中同步/锁的底层代码均有该方法出现, 后序再解析这个相关

      openjdk-os-interrupt

    4. hotspot/src/share/vm/runtime/osThread.hpp中的set_interrupted

      openjdk-os-thread

  • Thread中的isInterrupted(boolean ClearInterrupted)

    1. 找到OpenJDK的jdk/src/share/native/java/lang/Thread.c

      openjdk-thread-isinterrupted

    2. 找到hotspot/src/share/vm/prims/jvm.cpp中的JVM_Interrupt

      可见clear_interrupted为是否中断标记位

      openjdk-jvm-cpp-is-interrupted

    3. 追到hotspot/src/os/linux/vm/os_linux.cpp中的os::is_interrupted

      可见根据是否清除中断标记来执行要不要设置中断标记为false

      openjdk-os-is-interrupted

参考资料

How to Handle InterruptedException in Java

OpenJDK