ThreadPoolExecutor 任务的中断状态是否会影响后续任务
ThreadPoolExecutor 任务的中断状态是否会影响后续任务
- 实验
- 从源码中找原因
- 总结
在使用线程池时,如果调用 future.cancel(true) 会中断工作线程。如果被取消的任务没有处理中断状态,即没有调用类似 Thread.sleep() 等会抛出 InterruptedException 异常的方法,又没有通过 Thread.interrupted() 清除中断状态,那中断状态可能会被同一个工作线程执行的下一个任务捕捉到,进而导致任务被莫名其妙的中断。
实验
为了验证中断状态是否会影响后续任务有又以下测试代码。
- 使用单线程的线程池保证任务都是在一个线程中执行。
- 提交两个任务,并打印中断状态
- 第一个任务是简单的 for 循环,在任务开始执行后 500ms 调用
future1.cancel(true)中断任务1。 - 等待任务 1 执行完成后再提交任务 2,任务 2 执行
Thread.sleep(10 * 1000),如果中断状态能被任务 2 捕捉到,则会中断sleep()并打印 “线程被中断”。
- 第一个任务是简单的 for 循环,在任务开始执行后 500ms 调用
package com.example.jdk8;import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;@Slf4j
public class Test {public static void main(String[] args) throws InterruptedException {// 单线程的线程池ExecutorService executor = Executors.newSingleThreadExecutor();Future<?> future1 = executor.submit(() -> {log.info("任务 1 开始, 中断状态: {}", Thread.currentThread().isInterrupted());// 运行时间超过 500msfor (int i = 0; i < 10000000; i++) {Thread.yield();}log.info("任务 1 结束, 中断状态: {}", Thread.currentThread().isInterrupted());});Thread.sleep(500);future1.cancel(true);log.info("任务取消状态: {}", future1.isCancelled());// 等待任务 1 执行完成后再提交任务 2Thread.sleep(5 * 1000);log.info("提交任务 2");executor.execute(() -> {log.info("任务 2 开始, 中断状态: {}", Thread.currentThread().isInterrupted());try {Thread.sleep(10 * 1000);} catch (InterruptedException e) {log.info("线程被中断");}log.info("任务 2 结束, 中断状态: {}", Thread.currentThread().isInterrupted());});executor.shutdown();executor.awaitTermination(1000, TimeUnit.SECONDS);}
}
两个任务的执行线程都是 pool-1-thread-1,第三行任务 1 结束时的中断状态是 true,但任务 2 打印的中断状态都是 false,说明中断状态不会影响后续任务。
16:45:29.815 [pool-1-thread-1] INFO com.example.jdk8.Test -- 任务 1 开始, 中断状态: false
16:45:30.329 [main] INFO com.example.jdk8.Test -- 任务取消状态: true
16:45:33.464 [pool-1-thread-1] INFO com.example.jdk8.Test -- 任务 1 结束, 中断状态: true
16:45:35.330 [main] INFO com.example.jdk8.Test -- 提交任务 2
16:45:35.330 [pool-1-thread-1] INFO com.example.jdk8.Test -- 任务 2 开始, 中断状态: false
16:45:45.343 [pool-1-thread-1] INFO com.example.jdk8.Test -- 任务 2 结束, 中断状态: false
从源码中找原因
任务 1 结束时的中断状态是 true,但任务 2 的中断状态都是 false,说明在中间某个环境中断状态被重置了。
下面是 ThreadPoolExecutor.runWorker() 方法的源码, while 循环是工作线程的核心逻辑,不断在阻塞队列中 getTask(),然后 task.run()。在第 10、11 行有重要的注释:如果线程池正在关闭,要确保线程被中断;如果不是的话,要确保线程没有被中断。测试代码中的线程池状态是正常运行的,满足第 11 行注释的情况。
runStateAtLeast(ctl.get(), STOP) 为 false,所以会走到 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)),Thread.interrupted() 会返回当前的中断状态并将中断状态重置为 false,所以任务 2 打印的中断状态为 false。
final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) {w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted. This// requires a recheck in second case to deal with// shutdownNow race while clearing interruptif ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);try {task.run();afterExecute(task, null);} catch (Throwable ex) {afterExecute(task, ex);throw ex;}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}
}
总结
被同一工作线程执行的任务,前面任务的中断状态不会影响后续任务,因为在执行任务前会调用 Thread.interrupted() 将中断状态重置为 false。
