前言
JAVA
中的线程,有一个状态叫做中断状态,用于标记线程是否被中断过。通过对线程中断状态的判断,可以实现一些例如优雅终止线程,唤醒线程等功能。
在Thread
类中有interrupt()
,interrupted()
和isInterrupted()
方法与线程的中断有关,本篇文章将对这些方法的具体作用进行详细解释。在文章的最后,还会通过一个典型例子,演示如何通过线程的中断状态来优雅的终止线程。
正文
一. Thread#interrupt
方法详解
Thread
类提供了interrupt()
方法来中断线程,哪个线程对象的interrupt()
方法被调用,那么这个线程就会被中断。关于interrupt()
方法,有如下注意点。
interrupt()
方法是成员方法;- 通常不能在线程内部调用线程的
interrupt()
方法来中断线程自己; - 如果线程正阻塞在
Object#wait()
,Object#wait(long)
,Object#wait(long, int)
,Thread#join()
,Thread#join(long)
,Thread#join(long, int)
,Thread#sleep(long)
或Thread#sleep(long, int)
方法上,然后被interrupt()
方法中断,此时中断状态会被重置为false(false表示未被中断),并且被中断的线程会收到中断异常InterruptedException
。
上面最重要的就是第3
点,第3
点中罗列出来的方法在使用时都需要try-catch
中断异常InterruptedException
,但是在这个中断异常被抛出前,线程的中断状态会被重置为false,这就造成一种现象:因为被中断而从阻塞方法中唤醒的线程的中断状态是false。这一点请切记。
二. Thread#interrupted
方法详解
Thread
类提供了静态方法interrupted()
来得到调用该方法的线程的中断状态。关于interrupted()
方法,有如下注意点。
interrupted()
方法是静态方法;- 哪个线程调用
interrupted()
方法,就会返回这个线程的中断状态,然后线程的中断状态会重置为false。
interrupted()
方法在调用后,调用线程的中断状态会重置为false,这一点请切记。
三. Thread#isInterrupted()
方法详解
Thread
类提供了isInterrupted()
方法来得到线程的中断状态,哪个线程对象的isInterrupted()
方法被调用,就会返回这个线程的中断状态。关于isInterrupted()
方法,有如下注意点。
isInterrupted()
方法是成员方法;- 哪个线程对象的
isInterrupted()
方法被调用,就会返回这个线程的中断状态,并且中断状态不会重置为false。
isInterrupted()
方法和interrupted()
方法都能获取到线程的中断状态,不同点在于:isInterrupted()
方法获取到线程的中断状态后,线程的中断状态不会重置为false,而interrupted()
方法会。
四. 优雅的终止线程
首先明确一点:一个线程的终止,不能由其它线程来强行终止。这也就是为什么Thread#stop()
,Thread#suspend
等方法会被废弃。
一个优雅终止线程的简单思路就是:在线程执行的任务中不断的判断一个标志位,当标志位满足某种条件时,任务结束运行,从而线程优雅终止。如下是一个简单示例。
public class InterruptedTest {
@Test
public void 优雅终止线程的简单示例() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 执行任务
for (int i = 0; i < 5; i++) {
threadPool.execute(() -> {
Thread thread = Thread.currentThread();
while (!thread.isInterrupted()) {
// empty run
}
System.out.println(thread.getName() + " 任务执行完毕");
});
}
// 主线程等待2秒,等待任务充分执行
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
// shutdownNow线程池
threadPool.shutdownNow();
// 主线程等待1秒,等待任务关闭
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}
上述示例中,每个线程执行的任务中会一直判断当前线程的中断状态,如果中断状态为true(表示线程被中断了),那么就退出while
循环,从而任务执行完毕,最终线程被优雅终止。运行测试程序,打印如下。
运行结果表明线程确实被终止了。上述示例中线程执行的任务实际上是响应中断
的,因此在线程被调用interrupt()
方法时,能够响应中断,从而结束任务的执行,最终线程终止。再看如下一个例子,是一个看起来响应中断,却实际不响应中断的典型错误案例。
public class InterruptedTest {
@Test
public void 线程池中的任务不响应中断的典型案例() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 执行任务
for (int i = 0; i < 5; i++) {
threadPool.execute(() -> {
Thread thread = Thread.currentThread();
while (!thread.isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(thread.getName() + " 被中断了");
}
}
System.out.println(thread.getName() + " 任务执行完毕");
});
}
// 主线程等待2秒,等待任务充分执行
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
// shutdownNow线程池
threadPool.shutdownNow();
// 主线程等待1秒,等待任务关闭
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}
上述示例中,任务大部分时间是阻塞在Thread#sleep(long)
方法上的,但其实Thread#sleep(long)
方法是响应中断的,所以上述示例是希望线程被中断时,线程从Thread#sleep(long)
方法返回,然后判断线程的中断状态,然后结束任务的运行,最终线程终止。那么运行测试程序,看一下运行结果。
可见任务没有结束运行,故线程也并没有被终止。原因就是第一节中提到的,线程阻塞在Thread#sleep(long)
方法上时如果被中断,那么中断状态会被重置为false然后抛出中断异常InterruptedException
,那么上述示例中,任务在判断线程的中断状态时,中断状态会恒为false,故任务永远不会退出。改进如下。
public class InterruptedTest {
@Test
public void 线程池中的任务不响应中断的典型案例改进() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 执行任务
for (int i = 0; i < 5; i++) {
threadPool.execute(() -> {
Thread thread = Thread.currentThread();
while (!thread.isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// 重新将中断状态置为true
thread.interrupt();
System.out.println(thread.getName() + " 被中断了");
}
}
System.out.println(thread.getName() + " 任务执行完毕");
});
}
// 主线程等待2秒,等待任务充分执行
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
// shutdownNow线程池
threadPool.shutdownNow();
// 主线程等待1秒,等待任务关闭
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}
改进思路就是在任务中捕获到InterruptedException
后,再调用一次当前线程对象的interrupt()
方法,来将当前线程的中断状态重新置为true,从而任务可以顺利结束运行,最终线程终止。运行测试程序,结果如下。
可见任务是顺利执行完毕,从而线程完成了优雅终止。
总结
关于JAVA
中线程的中断
,总结如下。
Thread
类提供了interrupt()
成员方法来中断线程,哪个线程对象的interrupt()
方法被调用,那么这个线程被中断,中断状态会被置为true。但是如果线程是阻塞在Object#wait
,Thread#sleep
等方法上时被调用interrupt()
方法来中断,那么中断状态会被重置为false,然后抛出中断异常InterruptedException
;Thread
类提供了interrupted()
静态方法来获取线程的中断状态,哪个线程调用interrupted()
方法,就返回这个线程的中断状态,同时也会
重置这个线程的中断状态为false;Thread
类提供了isInterrupted()
成员方法来获取线程的中断状态,哪个线程对象的isInterrupted()
方法被调用,那么就返回这个线程的中断状态,并且不会
重置这个线程的中断状态为false;- 线程的优雅终止需要线程执行的任务能够根据某种条件来结束运行,从而终止线程。一个简单的思路是在线程执行的任务中不断的判断一个标志位,当标志位满足某种条件时,任务结束运行,从而线程优雅终止。