【问题标题】:What could be the cause of RejectedExecutionExceptionRejectedExecutionException 的原因可能是什么
【发布时间】:2012-01-01 06:19:13
【问题描述】:

我在我的 tomcat 服务器 (+liferay) 上遇到此异常

java.util.concurrent.RejectedExecutionException

我的课是这样的:

public class SingleExecutor extends ThreadPoolExecutor {
  public SingleExecutor(){
    super(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
  }

  @Override
  public void execute(Runnable command) {
    if(command instanceof AccessLogInsert){
        AccessLogInsert ali = (AccessLogInsert)command;
        ali.setConn(conn);
        ali.setPs(ps);
    }
    super.execute(command);
  }
}

我在super.execute(command); 线上得到了这个异常 当队列已满但 LinkedBlockingQueue 大小为 2^31 时,可能会出现此错误,并且我确信没有那么多命令等待。

一开始一切都很稳定,但在我重新部署战争后它开始发生。这个类不是战争的一部分,而是在 tomcat/lib 的一个 jar 中。

您知道为什么会发生这种情况以及如何解决吗?

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    来自ThreadPoolExecutorJavaDoc(强调我的)

    execute(java.lang.Runnable) 方法中提交的新任务将被拒绝Executor 已关闭,以及当Executor 对最大线程和工作队列容量使用有限界限时,并且是饱和的。无论哪种情况,execute 方法都会调用其RejectedExecutionHandlerRejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。提供了四个预定义的处理程序策略:

    1. 在默认的ThreadPoolExecutor.AbortPolicy 中,处理程序在拒绝时抛出运行时RejectedExecutionException
    2. ThreadPoolExecutor.CallerRunsPolicy 中,调用execute 的线程自己运行任务。这提供了一种简单的反馈控制机制,可以减慢新任务的提交速度。
    3. ThreadPoolExecutor.DiscardPolicy 中,简单地丢弃了一个无法执行的任务。
    4. ThreadPoolExecutor.DiscardOldestPolicy中,如果executor没有关闭,工作队列头部的任务被丢弃,然后重试执行(可能再次失败,导致重复。)

    可以定义和使用其他类型的RejectedExecutionHandler 类。这样做需要小心谨慎,尤其是当策略设计为仅在特定容量或排队策略下工作时。

    因此,据推测,重新加载战争会触发 Executor 的关闭。尝试将相关库放入战争中,以便 Tomcat 的ClassLoader 有更好的机会正确重新加载您的应用。

    【讨论】:

    • 答案的最后一部分很好。
    • “当 Executor 关闭时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝。” 这导致了我的代码中的一个错误,我通过以下方式解决了这个错误关闭后线程休眠 500 毫秒(这可能不是必需的),然后将调度程序设置为 null,以便下次需要运行任务时,相关方法会检查调度程序是否为 null。如果是,则创建一个新的。这样就消除了因关闭而导致的拒绝。
    • @AgiHammerthief 不需要休眠,但需要适当的并发控制。此外,虽然您的“解决方案”可能会阻止崩溃,但它只是隐藏了听起来像是一个很大的资源所有权问题。
    • @OrangeDog 解释得很好。但是假设在经过适当的测试后,我的应用程序永远不会发生这种情况。在生产中只发生过一次。是否是由其他外部因素引起的,例如数据库服务器关闭或 Kafka 服务器问题。增加最大池和队列容量是否可以解决这个问题?
    【解决方案2】:

    只是为了补充 OrangeDog 的出色答案,Executor 的合同确实是这样的,当执行程序饱和时(即队列中没有空间),它的 execute 方法将抛出 RejectedExecutionException

    但是,如果它改为阻塞会很有用,它会自动等待直到队列中有新任务的空间。

    使用以下自定义BlockingQueue 可以实现:

    public final class ThreadPoolQueue extends ArrayBlockingQueue<Runnable> {
    
        public ThreadPoolQueue(int capacity) {
            super(capacity);
        }
    
        @Override
        public boolean offer(Runnable e) {
            try {
                put(e);
            } catch (InterruptedException e1) {
                Thread.currentThread().interrupt();
                return false;
            }
            return true;
        }
    
    }
    

    这实质上实现了背压算法,每当执行器饱和时就会减慢生产者的速度。

    将其用作:

    int n = Runtime.getRuntime().availableProcessors();
    ThreadPoolExecutor executor = new ThreadPoolExecutor(0, n, 1, TimeUnit.MINUTES, new ThreadPoolQueue(n));
    for (Runnable task : tasks) {
        executor.execute(task); // will never throw, nor will queue more than n tasks
    }
    executor.shutdown();
    executor.awaitTermination(1, TimeUnit.HOURS);
    

    【讨论】:

    • CallerRunsPolicy 已经存在以实现这一目标,并且提供比简单阻塞更好的吞吐量。
    • 不,不是因为让生产者运行任务来阻止生产者是一种悲观;当生产者忙于单个任务时,它将使池饥饿。
    猜你喜欢
    • 2013-02-16
    • 2021-09-01
    • 2020-09-09
    • 2023-03-29
    • 2010-12-03
    • 2013-06-25
    • 1970-01-01
    • 1970-01-01
    • 2021-11-01
    相关资源
    最近更新 更多