单个执行程序服务中的RejectedExecutionException [英] RejectedExecutionException inside single executor service

查看:341
本文介绍了单个执行程序服务中的RejectedExecutionException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In one of our services, someone added such (simplified) a piece of code:

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,从而使它存活了太多时间.当然,为每个方法调用创建单独的执行程序服务都是很糟糕的,并且会被更改;但这正是为什么看到错误的原因.

Most of the time the error is OutOfMemoryError - which I perfectly understand. The person writing the code never invoked ExecutorService::shutDown, thus keeping it alive too much. Of course creating a separate executor service for each method call is bad and will be changed; but this is exactly why the error is seen.

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

The point that I do not understand is why RejectedExecutionException would be thrown, specifically it is being thrown here.

代码注释有道理:

  1. 如果我们无法将任务排队,则尝试添加一个新线程.如果失败,我们知道我们已关闭或饱和,因此拒绝该任务.
  1. If we cannot queue task, then we try to add a new thread. If it fails, we know we are shut down or saturated and so reject the task.

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

If this is indeed the case, how come the documentation of execute does not mention this?

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

If the task cannot be submitted for execution, either because this executor has been shutdown or because its capacity has been reached, the task is handled by the current RejectedExecutionHandler.

坦率地说,起初我虽然说ExecutorService是GC编写的-可访问性和范围是不同的东西,并且允许GC清除 不可访问的任何内容;但是有一个Future<?>可以强烈引用该服务,因此我将其排除在外.

To be frank initially I though that ExecutorService is GC-ed - reachability and scope are different things and GC is allowed to clear anything which is not reachable; but there is a Future<?> that will keep a strong reference to that service, so I excluded this.

推荐答案

您写过

坦率地说,我起初虽然说ExecutorService是GC编写的-可达性和范围是不同的东西,并且允许GC清除 无法达到的任何内容;但是有一个Future<?>会强烈引用该服务,因此我将其排除在外.

To be frank initially I though that ExecutorService is GC-ed - reachability and scope are different things and GC is allowed to clear anything which is not reachable; but there is a Future<?> that will keep a strong reference to that service, so I excluded this.

但这实际上是一个非常合理的场景,在 JDK-8145304 .在错误报告的示例中,ExecutorService并不保存在局部变量中,但是局部变量本身并不能防止垃圾收集.

But this is actually a very plausible scenario, which is described in JDK-8145304. In the bug report's example the ExecutorService is not held in a local variable, but a local variable does not prevent garbage collection per se.

请注意,异常消息

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.

supports this, as the state of ThreadPoolExecutor@6e005dc9 is specified as Terminated.

关于期货持有对其创建ExecutorService的引用的假设是错误的.实际类型取决于服务实现,但对于常见的实现,它将是

The assumption that futures hold a reference to their creating ExecutorService is wrong. The actual type depends on the service implementation, but for the common ones, it will be an instance of FutureTask which has no reference to an ExecutorService. It's also visible in the exception message that this applies to your case.

即使有引用,创建者也将是实际的ThreadPoolExecutor,但这是包装的FinalizableDelegatedExecutorService实例,它会收集垃圾并在ThreadPoolExecutor实例上调用shutdown()(通常,薄包装是好的在优化的代码中过早地进行垃圾回收的候选对象,而这些代码只是绕过了包装.

Even if it had a reference, the creator would be the actual ThreadPoolExecutor, but it is the wrapping FinalizableDelegatedExecutorService instance which gets garbage collected and calls shutdown() on the ThreadPoolExecutor instance (Thin wrappers are generally good candidates for premature garbage collection in optimized code which just bypasses the wrapping).

请注意,尽管错误报告仍处于打开状态,但实际上该问题已在JDK 11中解决.在那里,FinalizableDelegatedExecutorService的基类,DelegatedExecutorService类具有一个如下所示的execute实现:

Note that while the bug report is still open, the problem is actually fixed in JDK 11. There, the base class of FinalizableDelegatedExecutorService, the class DelegatedExecutorService has an execute implementation that looks like this:

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

这篇关于单个执行程序服务中的RejectedExecutionException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆