java - 单个执行器服务中的 RejectedExecutionException
问题描述
在我们的一项服务中,有人添加了这样(简化的)一段代码:
public class DeleteMe {
public static void main(String[] args) {
DeleteMe d = new DeleteMe();
for (int i = 0; i < 10_000; ++i) {
d.trigger(i);
}
}
private Future<?> trigger(int i) {
ExecutorService es = Executors.newSingleThreadExecutor();
Future<?> f = es.submit(() -> {
try {
// some long running task
Thread.sleep(10_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return f;
}
}
这有时会失败:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3148f668 rejected from java.util.concurrent.ThreadPoolExecutor@6e005dc9[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at java.util.concurrent.Executors$DelegatedExecutorService.submit(Executors.java:678)
at com.erabii.so.DeleteMe.trigger(DeleteMe.java:29)
at com.erabii.so.DeleteMe.main(DeleteMe.java:22)
大多数时候错误是OutOfMemoryError
- 我完全理解。编写代码的人从未调用过ExecutorService::shutDown
,因此使其保持活跃。当然,为每个方法调用创建一个单独的执行器服务是不好的,并且会被更改;但这正是出现错误的原因。
我不明白的一点是为什么RejectedExecutionException
会被抛出,特别是它被扔在这里。
那里的代码注释很有意义:
- 如果我们不能排队任务,那么我们尝试添加一个新线程。如果它失败了,我们知道我们已经关闭或饱和,因此拒绝任务。
如果确实如此,为什么文档中execute
没有提到这一点?
如果任务无法提交执行,要么是因为这个执行器已经关闭,要么是因为它的容量已经达到,任务由当前的 RejectedExecutionHandler 处理。
坦率地说,最初我虽然这ExecutorService
是 GC 版 - 可达性和范围是不同的东西,并且允许 GC 清除任何无法访问的东西;但是有一个Future<?>
会强烈引用该服务,所以我排除了这个。
解决方案
你写了
坦率地说,最初我虽然那
ExecutorService
是 GC-ed - 可达性和范围是不同的东西,并且允许 GC 清除任何无法访问的东西;但是有一个Future<?>
会强烈引用该服务,所以我排除了这个。
但这实际上是一个非常合理的场景,在JDK-8145304中有描述。在错误报告的示例中,ExecutorService
它没有保存在局部变量中,但局部变量本身并不能阻止垃圾收集。
注意异常信息
Task java.util.concurrent.FutureTask@3148f668 rejected from
java.util.concurrent.ThreadPoolExecutor@6e005dc9[Terminated,
pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
支持这一点,因为 的状态ThreadPoolExecutor@6e005dc9
被指定为Terminated
。
期货持有对其创造的参考的假设ExecutorService
是错误的。实际类型取决于服务实现,但对于常见的,它将是一个FutureTask
不引用ExecutorService
. 在适用于您的案例的异常消息中也可以看到。
即使它有一个引用,创建者也将是实际的ThreadPoolExecutor
,但它是包装FinalizableDelegatedExecutorService
实例,它会收集垃圾并调用shutdown()
实例ThreadPoolExecutor
(瘦包装器通常是优化代码中过早垃圾收集的良好候选者,它只是绕过包装)。
请注意,虽然错误报告仍处于打开状态,但该问题实际上已在 JDK 11 中得到修复。在 的基类中FinalizableDelegatedExecutorService
,该类DelegatedExecutorService
的execute
实现如下所示:
public void execute(Runnable command) {
try {
e.execute(command);
} finally { reachabilityFence(this); }
}
推荐阅读
- android - Android 在 AsyncTask 使用 GSON 解析多级嵌套 JSON
- angularjs - angularjs 和 CI:预检响应无效(重定向)
- intellij-idea - 我应该告诉 Intellij 我的 WSL 文件系统区分大小写吗?
- sql - 如何将不同数据类型的两个值从行旋转到列?
- powershell - 如何从 ps1 文件运行 WinSCP
- amazon-web-services - 我可以在多行上放置一个 eb 扩展容器命令吗?如果可以的话?
- sql - 从视图创建临时表
- java - Spring Boot 单例获取值
- apache-spark - 如何获取 saveToCassandra 并使用它?
- reactjs - React 美丽的 dnd 不适用于 React Semantic UI Table