请看这个例子。我从我的生产项目中拿走它。 Webserver收到一个命令并启动新的Thread,它通过TheadPool开始计算。当用户想要结束计算时,他发送另一个命令来中断这个新线程,并且ThreadPool的工作者正在向下移动。它工作正常,但我不明白为什么。
public static void main(String[] args) throws Throwable {
final ExecutorService p = Executors.newFixedThreadPool(2);
System.out.println("main say: Hello, I'm Main!");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " say: Starting monitor");
Thread monitor = new Thread(new Runnable() {
@Override
public void run() {
try {
while(true) {
Thread.sleep(1500);
System.out.println(Thread.currentThread().getName() + " say: I'm still here...hahahahah");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " say: Bye for now!");
}
}
},"monitor");
monitor.setDaemon(true);
monitor.start();
List<Callable<Integer>> threads = new ArrayList<>();
for (int i = 0; i < 5; i++) {
threads.add(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + " say: Hello!");
try {
for (int c = 0; c < 5; c++) {
System.out.println(Thread.currentThread().getName() + " say: " + c);
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " say: I'm interrupted :(");
}
System.out.println(Thread.currentThread().getName() + " say: Bye!");
return 0;
}
});
}
System.out.println(Thread.currentThread().getName() + " say: Starting workers");
try {
p.invokeAll(threads);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " say: I'm interrupted :(");
}
System.out.println(Thread.currentThread().getName() + " say: Bye!");
}
}, "new thread");
System.out.println("main say: Starting new thread");
t.start();
System.out.println("main say: Waiting a little...");
Thread.sleep(1250);
System.out.println("main say: Interrupting new thread");
t.interrupt();
// p.shutdown();
System.out.println(String.format("main say: Executor state: isShutdown: %s, isTerminated: %s",
p.isShutdown(),
p.isTerminated()));
System.out.println("main say: Bye...");
}
主要问题:当currentThread中断时,为什么ThreadPool会中断其工作者?我在哪里可以了解它的行为?
为什么在这个例子中主线程不会退出,但什么也不做? ThreadPool处于非活动状态,但未被终止,并且正在关闭并且不会处理剩余的任务。
答案 0 :(得分:2)
主要问题:当currentThread中断时,为什么ThreadPool会中断其工作者?我在哪里可以了解它的行为?
你过度概括了。 invokeAll()
的ExecutorService
方法会在中断时取消所有未完成的任务。这在the API docs中有记录。
如果您问“我怎么知道它会这样做”,那么文档就是您的答案。如果你问为什么界面是这样设计的,那么它是有意义的,因为当它被中断时,方法会抛出InterruptedException
而不是返回一个值,因此可以合理地假设那些进一步的工作未完成的任务可能会被浪费。
为什么在这个例子中主线程不退出,但什么都不做?
“主线程”是从main()
开始的那个。此线程 退出,在此之前它还会执行其他几项操作,包括创建,启动和中断线程,以及输出多条消息。它在控件到达main()
的末尾时退出。
但也许你的意思是主线程直接启动线程“新线程”。该线程还可以执行多项操作,包括启动监视器线程并将作业提交给执行程序服务。或者你可能会问为什么这个线程在ExecutorService
正在工作时没有退出,但是为什么它在等待invokeAll()
方法返回时会退出?即使该方法返回Future
的列表,但是它的文档很清楚它阻止,直到提交给它的所有任务都完成,或者发生异常。
答案 1 :(得分:0)
ExecutorService.invokeAll()
的API中提到了您的任务中断:
抛出:
InterruptedException
- 如果在等待时被中断,在这种情况下,未完成的任务被取消
因此,当您致电p.invokeAll(threads)
期间收到中断时,threads
中的所有任务都会被取消。
API未指定是否使用mayInterruptIfRunning
调用Future.cancel()
,但是如果您查看code for AbstractExecutorService
,ThreadPoolExecutor
继承invokeAll()
的实现{ {1}},您可以看到在启用中断的情况下取消了任务:
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
/* ... */
try {
/* ... */
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
我认为这比没有中断取消它们更有意义,因为已经有一个中断;这只是“宣传它”。
程序没有退出,并且线程池没有关闭或终止,因为你根本就没有告诉它关闭它。
所以这与以下简化程序没有什么不同:
public static void main(String[] args) throws Throwable {
final ExecutorService p = Executors.newFixedThreadPool(2);
p.execute(new Runnable() { public void run() { } });
Thread.sleep(1000);
System.out.println(String.format("main say: Executor state: isShutdown: %s, isTerminated: %s",
p.isShutdown(),
p.isTerminated()));
}
当意味着关闭它们时,线程池没有任何特殊的魔法猜测;他们等到你实际告诉他们。 Executors.newFixedThreadPool()
州的文档:
池中的线程将一直存在,直到明确
shutdown
。
创建线程池时,需要确保最终清理它们。通常这是通过致电shutdown()
或shutdownNow()
。为什么这有必要?因为运行线程在Java垃圾回收的上下文中是特殊的。运行线程是确定哪些对象不会被垃圾收集的起点,并且在它们仍在运行时永远不会被垃圾收集。并且在仍有运行线程的情况下,Java程序永远不会退出(当然,除非您调用System.exit()
。)
某些特殊情况,其中线程池可能没有正在运行的线程,因此会被垃圾回收。 API docs for ThreadPoolExecutor
解释了这一点:
定稿
程序中不再引用且没有剩余线程的池将自动关闭。如果您希望确保即使用户忘记调用
shutdown()
也会回收未引用的池,那么您必须通过设置适当的保持活动时间,使用零核心线程的下限来安排未使用的线程最终死亡/或设置allowCoreThreadTimeOut(boolean)
。
所以我们可以修改上面的例子,最终像这样退出:
final ThreadPoolExecutor p = new ThreadPoolExecutor(
0, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
或者这个:
final ThreadPoolExecutor p = new ThreadPoolExecutor(
2, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
p.allowCoreThreadTimeOut(true);
但是,当你完成线程池时,调用shutdown
或shutdownNow
通常更简洁,而不是依赖于超时。