当你想取消一个正在运行的executor任务时,这个用例并不罕见。例如,您想要停止正在进行的下载,或者您想要取消正在进行的文件复制。所以你有:
1 2 3 4 5 6 7 8 9 10 11 |
|
不幸的是,这不起作用。打电话shutdownNow()
或者cancel()
不会停止正在进行的运行。这些方法只是简单地调用.interrupt()
在各自的螺纹上。问题是,您的runnable不能处理InterruptedException
(而且也不能)。这是许多书籍和文章中描述的一个非常常见的问题,但仍然有点违反直觉。
那你是做什么的?您需要一种方法来停止缓慢或阻塞的操作。如果你有一个长的/无限的循环,你可以只添加一个条件是否Thread.currentThread().isInterrupted()
如果是的话就不要继续了。然而,通常情况下,阻塞发生在代码之外,所以您必须指示底层代码停止。通常这是通过关闭流或断开连接来实现的。但是为了做到这一点,你需要做很多事情。
- 扩展
Runnable
- 使“可取消的”资源(例如输入流)成为实例字段
- 提供一个
cancel
方法添加到您的扩展runnable,在这里您获得“可取消的”资源并取消它(例如调用inputStream.close()
) - 实施一种习俗
ThreadFactory
这反过来创造了习惯Thread
重写interrupt()
方法并调用cancel()
方法在您的扩展Runnable
- 用自定义线程工厂实例化执行器(静态工厂方法将其作为参数)
- 在中处理阻塞资源的突然关闭/停止/断开
run()
方法
坏消息是,您需要在线程工厂中访问特定的可取消runnable。你不能使用instanceof
检查它的类型是否合适,因为执行程序将您提交给它们的可运行文件包装在Worker
不公开其底层可运行程序的实例。
对于单线程执行器来说,这很简单——只需在最外层的类中保存一个对当前提交的runnable的引用,并在interrupt
方法,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
(CancellableRunnable
是一个自定义接口,它只定义了cancel()
方法)
但是,如果您的执行器必须同时运行多个任务,会发生什么情况呢?如果你想全部取消,那么你可以保留一个已提交的列表CancellableRunnable
实例,并在被中断时简单地取消它们。因此,runnables将被取消多次,所以你必须考虑到这一点。
如果你想要精细的控制,例如通过取消特定的期货,那么没有简单的解决方案。你甚至不能扩展ThreadPoolExecutor
因为addWorker
方法是私有的。你必须复制粘贴它。
唯一的选择是不要依赖future.cancel()
或者executor.shutdownAll()
取而代之的是保留你自己的清单CancellableFuture
实例,并将它们映射到相应的未来。因此,每当您想要取消一些(或所有)可运行程序时,您可以反过来做——获取您想要取消的可运行程序,调用.cancel()
(如上图),然后获取其对应的Future
,也取消它。类似于:
1 2 3 4 5 6 7 |
|
(不使用runnable作为键,您可以使用在用例中有意义的标识符,并将runnable和future作为值存储在该键下)
这是一个巧妙的解决方法,但是无论如何,我已经提交了一个增强java.util.concurrent包的请求,所以在未来的版本中,我们可以选择管理这个用例。