关闭线程池时Thread.isInterrupted()注意事项
今天在研究方案时随手写了个小demo,想要验证线程和任务的控制问题,然后踩了个小坑,虽然这个根因是自己没看过相关源码导致的,但是还是决定记下来,因为我搜这个问题的时候,百度谷歌都没有相关答案。
先上结论
如果在线程池中,投递了线程(这里指的是java.lang.Thread
,以及它的派生类),那么使用当想要判断线程是否中断时,记得要用Thread.currentThread().isInterrupted()
,而不是用this.isInterrupted()
源码
目录结构如下:
1 | Main.java |
内容如下:Main.java
是主线程,主线程会起一个线程: ExecutorCallerThread
,这个线程里面会起一个线程池。线程池里面被提交了4个同样的任务线程(ExecutorWorkingThread
),但是线程池大小为2,任务却有4个,且每个任务是无限执行、不会自然退出的,那么意味着只有两个任务能得到执行,另外两个在排队。
上述状态达到后(比如程序开始1秒),主线程开始停掉 ExecutorCallerThread
, ExecutorCallerThread
会尝试用ExecutorService.shutdownNow()
把线程池里面的进程也停掉。
期望的结果:ExecutorCallerThread
退出
线程池中的4个ExecutorWorkingThread
,不管是正在执行的2个,还是正在排队的2个,都退出。
Main.java
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.ruiruigeblog.threadtest;
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorCallerThread callerThread = new ExecutorCallerThread();
callerThread.start();
// 等1秒,等各种子线程、子线程的子线程都开始工作,然后再尝试停止它们
Thread.sleep(1000);
// 打断子线程
callerThread.interrupt();
while (true) {
Thread.sleep(1000);
}
}
}
ExecutorCallerThread.java
:
1 | package com.ruiruigeblog.threadtest; |
ExecutorWorkingThread.java
:1
2
3
4
5
6
7
8
9
10
11
12package com.ruiruigeblog.threadtest;
public class ExecutorWorkingThread extends Thread {
public void run() {
// 本线程是死循环,除非被打断,否则无法正常退出
while (!isInterrupted()) {
System.out.println(Thread.currentThread().getName() + ": is working");
}
System.out.println(Thread.currentThread().getName() + ": exit by interrupt");
}
}
运行与分析
结果运行之后,程序一直运行,各种线程并没有停下来:
这是怎么回事呢?翻了下源码才发现自己有点脑残了。。。
首先,我的思路是大致没错的:
- 主线程interrupt
ExecutorCallerThread
ExecutorCallerThread
当时应该阻塞与执行:ExecutorService.awaitTermination(long timeout, TimeUnit unit)
,等待线程池里的任务执行完毕,阻塞结束。- 当步骤1发生是,步骤2里面的
ExecutorService.awaitTermination(long timeout, TimeUnit unit)
阻塞会停止,收到一个InterruptedException
,我们catch住这个异常后,通过shutdownNow()
方法:- 停止接受新任务:这点没问题,本来就没有新任务提交过来
- 停止排队中的任务:这点也没问题,2个未开始的任务给踢掉
- 打断正在执行的任务:对于正在执行的2个任务,线程池会进行打断,然后我的工作线程会发现打断,自己退出
那么一切看起来都没问题,问题出在哪里呢?
答案就是:ExecutorService.submit
方法,实际上是Future<?> submit(Runnable task);
,它接收的是一个Runnable
对象,也把它当作Runnable
来对待的。接收后,在线程池内部,创建一个新的Thread
,把Runnable
当作参数,传递给Thread
的构造函数去了。
注意,这里我用的是
Executors.newFixedThreadPool()
,因此返回的是java.util.concurrent.ThreadPoolExecutor
具体代码如下:java.util.concurrent.ThreadPoolExecutor$Worker
:1
2
3
4
5
6
7
8
9/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
可以看到,调用链如下:1
2
3
4
5AbstractExecutorService.submit(Runnable):
java.util.concurrent.ThreadPoolExecutor.execute(Runnable):
java.util.concurrent.ThreadPoolExecutor.addWorker(Runnable, boolean):
java.util.concurrent.ThreadPoolExecutor$Worker()
this.thread = getThreadFactory().newThread(this);
换句话说,我们自己的ExecutorWorkingThread
,根本就没有被创建,被创建的是另一个线程,它包住了我们的线程。那么当我们调用this.isInterrupted()
的时候,返回的自然是false,因为线程池interrupt的,是那个包装thread。
当我们把ExecutorWorkingThread.java
中的:1
while (!isInterrupted())
改成:1
while (!Thread.currentThread.isInterrupted())
结果就正确了:
另外,如果投递的直接是Runnable
,也不会有这个问题,因为Runnable
自身就没有isInterrupted()
方法,这也可以扯到向上造型向下造型什么的,那个可能就扯远了。
基本功还是要扎实啊,晕