Java线程池的拒绝策略

1、简介

  jdk1.5 版本新增了JUC并发编程包,极大的简化了传统的多线程开发。前面文章中介绍了线程池的使用,连接地址:http://www.javashuo.com/article/p-wgyamqkp-kh.htmlhtml

  Java线程池,是典型的池化思想的产物,相似的还有数据库的链接池、redis的链接池等。池化思想,就是在初始的时候去申请资源,建立一批可以使用的链接,这样在使用的时候,就没必要再进行建立链接信息的开销了。举个生活中鲜明的例子,在去著名洋快餐某基或者某劳的时候,配餐人员是从一个中间的保温箱中直接取食材,而后打包就行了。不用再临时的来了一个单子,又要去拿原材料,又要去进行加工。效率明显的就是提升了不少。java

  俗话说 满而不损则溢,盈而不持则倾。线程池既然是容器,那么必然的会有存满的状况。在达到某些特定条件的时候,再来请求的话,池子是如何进行请求处理的呢?这里就引出了池的拒绝策略。通常的数据库链接池在达到最大链接数的时候会默认的等待特定的设置的时间或者直接就抛出异常。而本文中要阐述的线程池却并不是如此的策略,下面开始展开讲解下。redis

2、线程池的拒绝策略

  线程池中,有三个重要的参数,决定影响了拒绝策略:corePoolSize - 核心线程数,也即最小的线程数。workQueue - 阻塞队列 。 maximumPoolSize - 最大线程数数据库

  当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。编程

  总结起来,也就是一句话,当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略多线程

3、拒绝策略定义

  拒绝策略提供顶级接口 RejectedExecutionHandler ,其中方法 rejectedExecution 即定制具体的拒绝策略的执行逻辑。并发

  jdk默认提供了四种拒绝策略:性能

                CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。通常并发比较小,性能要求不高,不容许失败。可是,因为调用者本身运行任务,若是任务提交速度过快,可能致使程序阻塞,性能效率上必然的损失较大测试

                AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,不然会打断当前的执行流程,影响后续的任务执行。spa

                DiscardPolicy - 直接丢弃,其余啥都没有

                DiscardOldestPolicy -  当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入

4、测试代码

  一、AbortPolicy 

package com.cfang;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class T2 {

    public static void main(String[] args) throws Exception{
        int corePoolSize = 5;
        int maximumPoolSize = 10;
        long keepAliveTime = 5;
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(10);
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, handler);
        for(int i=0; i<100; i++) {
            try {
                executor.execute(new Thread(() -> log.info(Thread.currentThread().getName() + " is running")));
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        executor.shutdown();
    }
}

   executor.execute()提交任务,因为会抛出 RuntimeException,若是没有try.catch处理异常信息的话,会中断调用者的处理流程,后续任务得不到执行(跑不完100个)。可自行测试下,很容易在控制台console中能查看到。

  二、CallerRunsPolicy 

    主体代码同上,更换拒绝策略:

RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

  运行后,在控制台console中可以看到的是,会有一部分的数据打印,显示的是 “main is running”,也即体现调用线程处理。

  三、DiscardPolicy 

    更换拒绝策略

RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();

    直接丢弃任务,实际运行中,打印出的信息不会有100条。

  四、DiscardOldestPolicy 

  一样的,更换拒绝策略:

RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();

  实际运行,打印出的信息也会少于100条。

5、总结

  四种拒绝策略是相互独立无关的,选择何种策略去执行,还得结合具体的业务场景。实际工做中,通常直接使用 ExecutorService 的时候,都是使用的默认的 defaultHandler ,也即 AbortPolicy 策略。

相关文章
相关标签/搜索