单个执行程序服务中的RejectedExecutionException

Eug*_*ene 4 java executorservice java-8

在我们的一项服务中,有人添加了这样的代码(简化):

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

有时由于以下原因而失败:

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)
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,错误是OutOfMemoryError-我完全理解。编写该代码的人从未调用过ExecutorService::shutDown,因此使它保持了太多的生命。当然,为每个方法调用创建单独的执行服务都是很糟糕的,并且会被更改;但这正是为什么看到错误的原因。

我不明白的是为什么RejectedExecutionException会被抛出,特别是在这里被抛出。

那里的代码注释一定道理:

  1. 如果我们无法将任务排队,则尝试添加一个新线程。如果失败,我们知道我们已关闭或已饱和,因此拒绝该任务。

如果确实如此,为什么文档execute中没有提及?

如果由于执行程序已关闭或已达到其能力而无法提交任务执行,则该任务由当前的RejectedExecutionHandler处理。

坦率地说,虽然我ExecutorService是由GC编写的-可访问性和范围是不同的东西,并且允许GC清除所有无法访问的内容;但是有一个Future<?>可以对该服务有很强的参考意义,因此我将其排除在外。

Hol*_*ger 7

你写了

坦率地说,虽然我ExecutorService是由GC编写的-可访问性和范围是不同的东西,并且允许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]
Run Code Online (Sandbox Code Playgroud)

支持,因为的状态ThreadPoolExecutor@6e005dc9指定为Terminated

期货参考其创建的假设ExecutorService是错误的。实际类型取决于服务实现,但对于常见的实现,它将是其实例,FutureTask而没有引用ExecutorService。这也适用于您的情况,在异常消息中也可见。

即使有引用,创建者也将是实际的ThreadPoolExecutor,但是包装FinalizableDelegatedExecutorService实例将被垃圾收集并调用shutdown()ThreadPoolExecutor实例(瘦包装器通常是绕过包装的优化代码中过早垃圾收集的良好候选者)。

请注意,尽管错误报告仍处于打开状态,但实际上该问题已在JDK 11中解决。在那里,的基FinalizableDelegatedExecutorServiceDelegatedExecutorService具有一个execute类似于以下内容的实现:

public void execute(Runnable command) {
    try {
        e.execute(command);
    } finally { reachabilityFence(this); }
}
Run Code Online (Sandbox Code Playgroud)