首页 > 解决方案 > 单个执行器服务中的 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会被抛出,特别是它被扔在这里

那里的代码注释很有意义:

  1. 如果我们不能排队任务,那么我们尝试添加一个新线程。如果它失败了,我们知道我们已经关闭或饱和,因此拒绝任务。

如果确实如此,为什么文档中execute没有提到这一点?

如果任务无法提交执行,要么是因为这个执行器已经关闭,要么是因为它的容量已经达到,任务由当前的 RejectedExecutionHandler 处理。

坦率地说,最初我虽然这ExecutorService是 GC 版 - 可达性和范围是不同的东西,并且允许 GC 清除任何无法访问的东西;但是有一个Future<?>会强烈引用该服务,所以我排除了这个。

标签: javajava-8executorservice

解决方案


你写了

坦率地说,最初我虽然那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,该类DelegatedExecutorServiceexecute实现如下所示:

public void execute(Runnable command) {
    try {
        e.execute(command);
    } finally { reachabilityFence(this); }
}

推荐阅读