前言
有时候我们需要中断一个线程。由于启动一个线程的方法是start()
,所以终止一个线程很容易想到stop()
,JDK在
java.lang.Thread
类中确实提供了一个stop()
方法,这个方法的作用是强制停止一个线程。但是从jdk1.2开始就被标记@Deprecated
废弃了,对应废弃的还有suspend()
和resume()
,因为强制停止一个线程不管该线程是否执行完成,并且会释放该线程持有的锁,因此会产生安全问题。
中断线程
一个线程不应该由其它线程来强制中断或停止,而是应该由线程自己停止。
interrupt()方法
每个线程会有一个boolean类型的中断状态来标识该线程是否需要被中断。java.lang.Thread
提供了Thread#interrupt()
方法来改变中断状态,该方法的作用并不是中断线程,而是通过把中断状态设置为true,来通知线程应该中断。但是是否执行中断,由线程自己决定。这才是中断线程的优雅实现,除了java.lang.Thread
提供的interrupt()
方法外,还有一种是自定义一个标记变量来标识线程是否应该被终止。
调用interrupt()方法时,线程可以处于不同的状态(不是中断状态,如果不清楚线程的状态,可以参考我之前的博客:Java线程到底有几种状态):
- 如果线程处于调用
sleep(...)
、join(...)
、wait(...)
等方法后的阻塞状态(包括WAITING、TIMED_WAITING状态),那么线程将立即退出被阻塞的状态,清除中断状态(中断状态设置为false),抛出InterruptedException
异常。
输出结果public static void main(String[] args) throws Exception { Thread thread = new Thread(() -> { try { Thread.sleep(5000); System.out.println("thread线程会被终止吗"); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.start(); // 主线程休眠100毫秒,让thread线程启动 Thread.sleep(100); System.out.println("thread状态:" + thread.getState().toString()); System.out.println(thread.isInterrupted()); thread.interrupt(); // 主线程休眠100毫秒,中断状态会被清理 Thread.sleep(100); System.out.println(thread.isInterrupted()); }
调用thread状态:TIMED_WAITING false java.lang.InterruptedException: sleep interrupted false
interrupt()
时,thread线程处于TIMED_WAITING状态,抛出了InterruptedException
,并且调用线程的中断状态也清理成了false,并且thread线程的任务也并没有执行完成。当然,调用interrupt()
方法前后,thread线程的中断状态都为false,所以有理由怀疑线程的中断状态一直是false,而不是false->true->false。关于这点,可以注释示例倒数第二行的Thread.sleep(100)
,多运行几次,最后一行输出有可能是true或者false。输出为true表示thread线程的中断状态还没来得及被清理就输出了,由此可以看出中断状态确实是false->true->false。 - 如果线程处于IO或者NIO中的Channel阻塞状态,IO操作会被中断或者返回特殊异常值。达到终止线程的目的。
- 如果都不满足,则会设置此线程的中断状态。
输出结果public static void main(String[] args) throws Exception { Thread thread = new Thread(() -> { while (true) { } }); thread.start(); // 主线程休眠100毫秒,让thread线程启动 Thread.sleep(500); System.out.println("thread状态:" + thread.getState().toString()); System.out.println(thread.isInterrupted()); thread.interrupt(); // 主线程休眠100毫秒,中断状态也不会被清理 Thread.sleep(100); System.out.println(thread.isInterrupted()); }
thread线程处于正常运行状态,调用thread状态:RUNNABLE false true
interrupt()
方法后仅仅只是设置了中断状态。
java.lang.Thread
除了提供interrupt()
方法外,还提供了几个相似或者说方法名相似的方法:
- isInterrupted (不会影响中断状态)
/** * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. * 检测线程是否已经被中断,中断状态是否重置取决于ClearInterrupted参数所传的值 */ private native boolean isInterrupted(boolean ClearInterrupted); /** * Tests whether this thread has been interrupted. The <i>interrupted * status</i> of the thread is unaffected by this method. * 检测当前线程是否已经被中断,该方法不会影响中断状态(interrupted status) * @revised 6.0 */ public boolean isInterrupted() { return isInterrupted(false); }
- interrupted (中断状态会被此方法清理,是不是有点想吐槽这个方法名)
/** * Tests whether the current thread has been interrupted. The * <i>interrupted status</i> of the thread is cleared by this method. In * other words, if this method were to be called twice in succession, the * second call would return false (unless the current thread were * interrupted again, after the first call had cleared its interrupted * status and before the second call had examined it). * 检测当前线程是否已经被中断,该线程的中断状态(interrupted status)会被此方法清理。 * 换句话说,如果这个方法连续两次被调用,第二次调用会返回false * (除非线程再次中断,在第一次调用已经清理中断状态之后且在第二次调用之前检测) * @revised 6.0 */ public static boolean interrupted() { return currentThread().isInterrupted(true); }
自定义变量
利用自定义变量来标识线程是否应该被终止,思想和interrput()
方法一样,只不过interrput()
方法中的中断状态是内置的
private static volatile Boolean interrupt = false;
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
while (!interrupt) {
// 执行线程本身的逻辑
}
// 执行线程中断后的逻辑
});
thread.start();
// 主线程休眠100毫秒,让thread线程启动
Thread.sleep(100);
// 中断线程,相当于调用interrupt()方法
interrupt = true;
}
注:interrupt变量为什么要加volatile
关键字,先挖个坑,有空再填。
实践
知道了如何中断线程和线程的中断状态的几个方法,具体到应用应该怎样用呢,应该遵循怎样的原则呢?
如果一个线程有被中断的需求,那么该线程有义务检测自己的状态,在适当的时候执行适当的逻辑。
例如:
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 执行线程本身的逻辑
}
// 执行线程中断后的逻辑
});
thread.start();
// 主线程休眠100毫秒,让thread线程启动
Thread.sleep(100);
// 中断线程thread
thread.interrupt();
}
线程里的任务可能会抛出InterruptException
,对于这个异常,线程可以自己处理,也可以向上抛,但是千万不要什么都不做(既不处理异常,也不向上抛)。因为这样调用者根本不知道线程中断了。异常向上抛也需要注意,此时线程的中断状态(interrupt status)可能已经被清除了,也就是interrupt status为false,所以向上抛之前需要调用一次interrupt()
方法。
补充
能响应中断事件的方法:
Object.wait()/wait(long)/wait(long, int)
Thread.sleep(long)/sleep(long, int)
Thread.join()/join(long)/join(long, int)
java.util.concurrent.BlockingQueue#take()/put(E)
java.util.concurrent.locks.Lock#lockInterruptibly()
java.util.concurrent.CountDownLatch#awit()
java.util.concurrent.CyclicBarrier#await()
java.util.concurrent.Exchanger#exchange(V)
java.util.concurrent.FutureTask#get()
java.nio.channels.InterruptibleChannel()
java.nio.channels.Selector
的相关方法