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

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

问题描述

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

公共类 DeleteMe {公共静态无效主(字符串 [] args){DeleteMe d = new DeleteMe();for (int i = 0; i <10_000; ++i) {d.触发器(i);}}私人未来<?>触发器(int i){ExecutorService es = Executors.newSingleThreadExecutor();未来<?>f = es.submit(() -> {尝试 {//一些长时间运行的任务线程睡眠(10_000);} catch (InterruptedException e) {e.printStackTrace();}});返回 f;}}

有时失败:

线程main"中的异常java.util.concurrent.RejectedExecutionException:任务java.util.concurrent.FutureTask@3148f668被java.util.concurrent.ThreadPoolExecutor@6e005dc9拒绝[已终止,池大小= 0,活动线程 = 0,排队任务 = 0,已完成任务 = 0]在 java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)在 java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)在 java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)在 java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)在 java.util.concurrent.Executors$DelegatedExecutorService.submit(Executors.java:678)在 com.erabii.so.DeleteMe.trigger(DeleteMe.java:29)在 com.erabii.so.DeleteMe.main(DeleteMe.java:22)

大多数时候错误是 OutOfMemoryError - 我完全理解.编写代码的人从未调用过 ExecutorService::shutDown,因此它一直处于活动状态.当然为每个方法调用创建一个单独的执行器服务是不好的,会被改变;但这正是看到错误的原因.

我不明白的一点是为什么 RejectedExecutionException 会被抛出,特别是它被抛出 这里.

代码注释 那里有点道理:

<块引用>

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

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

<块引用>

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

坦率地说,最初我认为 ExecutorService 是 GC ed的——可达性和范围是不同的东西,GC可以清除任何可达的东西;但是有一个 Future 将保持对该服务的强引用,所以我排除了这个.

解决方案

你写的

<块引用>

坦率地说,最初我认为 ExecutorService 是 GC ed的——可达性和范围是不同的东西,GC可以清除任何可达的东西;但是有一个 Future 将保持对该服务的强引用,所以我排除了这个.

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

注意异常信息

Task java.util.concurrent.FutureTask@3148f668 被拒绝java.util.concurrent.ThreadPoolExecutor@6e005dc9[已终止,池大小 = 0,活动线程 = 0,排队任务 = 0,已完成任务 = 0]

支持这个,因为ThreadPoolExecutor@6e005dc9的状态被指定为Terminated.

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

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

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

public void execute(Runnable command) {尝试 {e.执行(命令);} 最后{reachabilityFence(this);}}

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;
    }
}

This fails sometimes with:

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)

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.

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

Code comments there make some sense:

  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.

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

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.

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.

解决方案

You wrote

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.

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.

Note that the exception message

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]

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

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.

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).

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天全站免登陆