Java 线程中断
中断与阻塞
中断是一种线程协作机制,也就是说通过中断并不能直接中断另外一个线程,而需要被中断的线程自己处理中断,每个线程都有一个中断标志位,代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标志位置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。
线程中断与 stop 最大的区别是,stop 是由系统强制终止线程,而线程中断则是给目标线程发送一个中断信号,如果目标线程没有接收线程中断的信号并结束线程,线程则不会终止,具体是否退出或者执行其他逻辑由目标线程决定。
public static void test1() throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (!this.isInterrupted()) {
System.out.println("thread is running");
}
}
};
thread.start();
Thread.sleep(3000);
System.out.println("设置中断标志为true");
thread.interrupt();
System.out.println("线程中断");
}
Thread类中关于中断的方法如下
public void interrupt():中断线程,设置线程的中断标志位为truepublic static boolean interrupted():静态方法,返回当前线程的中断标记位,同时清除中断标记,改为false。比如当前线程已中断,调用此方法,返回true,同时将当前线程的中断标记位改为false, 再次调用interrupted(),会发现返回falsepublic boolean isInterrupted():检查线程的中断标记位,true-中断状态, false-非中断状态,其内部是直接调用另一个静态方法boolean isInterrupted(boolean *ClearInterrupted*),传递参数false,表示不清除中断状态private native boolean isInterrupted(boolean *ClearInterrupted*);:可以手动控制是否清除中断标记,但是由于是private的方法,不能直接调用
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
//由于线程在中断时不处于活动状态而被忽略的线程中断将通过此方法返回false来反映
//如果线程已经被中断,返回true,否则返回false
public boolean isInterrupted() {
return isInterrupted(false);
}
// 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);
通过Thread#interrupt可以发出中断请求
public void interrupt() {
// 如果其他线程想要中断本线程,需要先通过SecurityManager检查是否有相关访问权限
if (this != Thread.currentThread())
checkAccess();
//blockerLock是Thread类内部维护的final类型的Object对象实例
synchronized (blockerLock) {
Interruptible b = blocker; //来自于sun.nio.ch.Interruptible;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0(); //释放锁后又调一次interrupt0()
}
Thread内部定义了一个volatile Interruptible blocker
package sun.nio.ch;
public interface Interruptible {
public void interrupt(Thread t);
}
参考其JavaDoc
The object in which this thread is blocked in an interruptible I/O operation, if any.The blocker's interrupt method should be invoked after setting this thread's interrupt status.
在设置了此线程的中断状态后应该调用blocker的interrupt方法,而设置中断状态是通过interrupt0()这个native方法进行的
在JDK里没有实现Interruptible这个接口,而且通常情况下值为null,该字段的初始化是通过下面这个方法进行的,通过其注释可以知道:在非NIO场景下,这个属性的值始终为null。通过反射可以强行对其进行赋值
// Set the blocker field; invoked via jdk.internal.misc.SharedSecrets
// from java.nio code
static void blockedOn(Interruptible b) {
Thread me = Thread.currentThread();
synchronized (me.blockerLock) {
me.blocker = b;
}
}
针对线程处于由sleep,wait,join,LockSupport.park等方法调用产生的阻塞状态时,调用interrupt方法,会抛出异常InterruptedException,同时会清除中断标记位,自动改为false。
不可中断的阻塞
虽然许多阻塞操作(如I/O操作、等待锁等)都支持响应中断,但也有一些操作是不可中断的。对于这些情况,线程中断机制允许线程在等待这些操作完成时,能够检查到中断请求,并在操作完成后立即退出。
- java.io包中的同步Socket I/O
- java.io包中的同步I/O
- Selector的异步I/O
- sychronized加的锁
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage()); // sleep interrupted
}
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
Thread.sleep(100);
System.out.printf(" interrupt status : %s\n", t1.isInterrupted());
结论: 阻塞方法sleep响应中断,抛出InterruptedException,同时清除中断标记位为false
中断LockSupport.park阻塞线程
Thread t3 = new Thread(() -> {
log.debug("t3 park.....");
LockSupport.park();
log.debug("t3 unpark.....");
log.debug("interrupt status: [{}]", Thread.currentThread().isInterrupted());
log.debug("t3 第二次 park.....");
LockSupport.park();
log.debug("t3 中断位为true, park失效.....");
}, "t3");
t3.start();
Thread.sleep(1000);
t3.interrupt();
结论: 阻塞方法park响应中断, 不会抛出异常,同时不会清除中断标记位,仍为true。
Thread t2 = new Thread(() -> {
while (true) {
boolean isInterrupted = Thread.currentThread().isInterrupted();
if (isInterrupted) {
log.info("interrupt status: {}", isInterrupted);
break;
}
}
}, "t2");
t2.start();
Thread.sleep(500);
t2.interrupt();
Thread.sleep(100);
log.info(" thread status, {}, interrupt status : {}", t2.getState(), t2.isInterrupted());
结论: 打断正常运行的线程, 不会清空打断状态,同时线程结束后,重置中断状态位
中断异常 InterruptedException
InterruptedException 异常通常会在执行一个被中断的线程时抛出。当一个线程正在等待、睡眠或以其他方式被阻塞时,并且另一个线程通过调用该线程的 interrupt() 方法来请求中断它,那么当被阻塞的线程检测到中断请求时,它就会抛出 InterruptedException。
以下是一些常见的会触发 InterruptedException 的场景:
- 等待(Waiting):当一个线程调用了
Object类的wait()方法或者wait(long timeout)、wait(long timeout, int nanos)方法时,如果线程在等待期间被中断,那么wait()方法会抛出InterruptedException。 - 睡眠(Sleeping):当一个线程调用了
Thread.sleep(long millis)或Thread.sleep(long millis, int nanos)方法进入睡眠状态时,如果线程在睡眠期间被中断,那么sleep()方法会抛出InterruptedException。 - 等待某个条件(Condition Waiting):在Java并发包(
java.util.concurrent)中,Condition接口提供了类似Object监视器方法的功能,如await()、awaitNanos(long nanosTimeout)、awaitUntil(Date deadline)等。如果一个线程在这些方法调用时等待某个条件,并且该线程被中断,那么这些await()方法会抛出InterruptedException。 - 连接和通道(Connections and Channels):在Java的NIO(非阻塞I/O)中,当线程在阻塞的I/O操作(如选择器的选择操作、通道的读/写操作等)上等待时,如果线程被中断,那么这些操作可能会抛出
InterruptedException或其他中断相关的异常(具体取决于操作类型)。 - 其他阻塞操作:任何Java API中的阻塞方法,如果其文档指出在中断时会抛出
InterruptedException,那么在该线程被中断时,这些操作就会抛出该异常。
处理 InterruptedException 的最佳实践是捕获它,然后根据你的应用逻辑决定是重新抛出异常(可能包装成自定义的异常),还是处理中断并恢复执行(比如清理资源后退出循环)。
注意,一旦捕获了 InterruptedException,中断状态会被清除,除非你在捕获异常后重新设置了中断状态(通过调用 Thread.currentThread().interrupt())。这是因为中断是一种协作机制,如果方法捕获了中断但没有重新抛出异常或重新设置中断状态,那么调用者可能不知道中断已经发生。

浙公网安备 33010602011771号