我们的其中一个服务中,某人添加了以下(简化)代码:
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允许清除任何不可达的东西; 但是有一个Future<?>
会保留对该服务的强引用,因此我排除了这一点。
Future<?>
将保持对该服务的强引用。Future
只是由方法返回,但没有在其他任何地方使用/存储。也许JVM会看到这一点,并确定它是不可达的? - Jacob G.pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0
- 这意味着队列中还没有提交任何任务,这是在主线程中发生的。因此,主线程(而不是池中的线程)必须将任务放入队列中,然后释放执行到_不同的_线程 - 这甚至在这里都没有发生。 - EugenenewFixedThreadPool(1)
而不是newSingleThreadExecutor()
,是否会遇到相同的行为? - Jacob G.