线程池拒绝策略

  • 如果当前同时运行的线程数量达到最大线程数量并且队列中也已经被放满了任务时,ThreadPoolExecutor就会启动拒绝策略来处理新任务

1. 四种内置拒绝策略

  • ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException异常,默认拒绝策略
  • ThreadPoolExecutor.CallerRunsPolicy:调用执行者自己的线程运行任务,也就是直接在调用execute方法 的线程中运行run被拒绝的任务。如果执行程序已经关闭,则会丢弃该任务
  • ThreadPoolExecutor.DiscardPolicy:直接丢弃被拒绝的任务,不予任何处理
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试提交当前任务

2. 如果不允许丢弃任务,应该选择什么拒绝策略

  • 如果不允许丢弃任务,可以选择CallerRunsPolicy拒绝策略
  • CallerRunsPolicy源码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static class CallerRunsPolicy implements RejectedExecutionHandler {

    public CallerRunsPolicy() { }


    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    //只要当前程序没有关闭,就用执行execute方法的线程执行该任务
    if (!e.isShutdown()) {

    r.run();
    }
    }
    }
  • 这种拒绝策略可以执行到所有任务,只要当前程序不关闭就会使用执行execute方法的线程来执行该任务

3. CallerRunsPolicy的问题及解决方法

  • CallerRunsPolicy拒绝策略会导致耗时的任务用了主线程,导致线程池阻塞,进而导致后续任务无法及时执行,严重情况下会导致OOM
  • 解决方法:
    • 在内存允许的情况下增加阻塞队列BlockingQueue的大小
    • 调整线程池的最大线程数maximumPoolSize以充分利用CPU
    • 持久化思路
      • 设计一张任务表将任务存储到MySQL数据库中
      • Redis缓存任务
      • 将任务提交到消息队列中
  • 详见CallerRunsPolicy 拒绝策略有什么风险?如何解决?