可能是RejectedExecutionException的原因


74

我在雄猫服务器(+ 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中。

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

Answers:


84

ThreadPoolExecutor JavaDoc(重点是我的)

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

  1. 默认情况下ThreadPoolExecutor.AbortPolicy,处理程序RejectedExecutionException在拒绝时抛出运行时。
  2. 在中ThreadPoolExecutor.CallerRunsPolicy,调用执行自己的线程运行任务。这提供了一种简单的反馈控制机制,将降低新任务的提交速度。
  3. 在中ThreadPoolExecutor.DiscardPolicy,简单地删除了无法执行的任务。
  4. 在中ThreadPoolExecutor.DiscardOldestPolicy,如果未关闭执行程序,则将丢弃工作队列开头的任务,然后重试执行(这可能再次失败,从而导致重复执行此操作)。

可以定义和使用其他种类的RejectedExecutionHandler类。这样做需要格外小心,尤其是在设计策略仅在特定容量或排队策略下才能工作时。

因此,大概是重新加载战争会触发的关闭Executor。尝试将相关的库放入战场,以便TomcatClassLoader更有机会正确地重新加载您的应用程序。


2
答案的最后一部分是一个不错的答案。
拉文德拉·巴布

execute(java.lang.Runnable)关闭执行器后,将拒绝使用方法提交的新任务。” 这引起了我代码中的错误,我通过在关机后将线程休眠500毫秒(可能没有必要),然后将调度程序设置为来解决此问题null,以便下次需要运行任务时,使用该方法检查调度程序是否为null。如果是,则创建一个新的。因此,消除了由于停机原因而造成的拒绝。
Agi Hammerthief,

2
@AgiHammerthief不需要睡觉,但是需要适当的并发控制。此外,虽然您的“解决方案”可能会停止崩溃,但它只是隐藏了听起来像是一个巨大的资源所有权问题的问题。
OrangeDog

9

只是为了增加OrangeDog的出色答案,an的约定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) {
            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为了实现这一点,已经存在,并且比简单的阻塞提供了更好的吞吐量。
OrangeDog

不,不是因为通过让生产者运行任务来阻止生产者是一种悲观。当生产者忙于单个任务时,它将饿死池。
rustyx

请注意,仅通过捕获就InterruptedException可以中断线程。这很少是可取的。通常,Thread.currentThread().interrupt();在退出该catch块之前,通常应始终通过来重新中断。
nilskp
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.