首页 > 技术文章 > 线程池创建+拒绝策略

liqiliang1437 2020-10-30 20:15 原文

线程池

适合单个任务处理时间比较短

需要处理的任务数量很大

创建方式的选择:

线程池的创建方法有两种

  1. 使用Executors线程工具类 ,直接点 newXxxThreadPool (可以new四种)
  2. 一种是如下所示,手动创建线程池

线程池的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

关于使用哪一种方法创建,阿里开发手册中也提到了

第3条规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

第4条规定:线程池不允许使用Executors创建,而是通过ThreadPoolExecutor的方式创建,这样的处理方式能让编写代码的攻城狮更加明确线程池的运行规则,规避资源耗尽(OOM)的风险

所以最好使用第二种,采用手动创建线程池的方式

有什么区别和利害关系呢?

直接使用Executors工具类的话 可以创建四种线程,分别是

newFixedThreadPool()	线程数量固定的线程池
newCachedThreadPool()	可缓存线程的线程池
newScheduledThreadPool()	执行定时任务的线程池
newSingleThreadExecutor()	单线程线程池

有什么弊端呢?

先看看newFixedThreadPool()的构造函数

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

LinkedBlockingQueue 任务队列没有指定容量,说明可以添加大量的任务,如果大量任务堆积,可能会导致OOM

再看看newCachedThreadPool的构造函数

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

最大线程数设置成了Integer.MAX_VALUE,意味着可以创建大量线程,也可能导致OOM

所以综上所述,要使用手动创建线程池的方式

手动创建线程池:

线程池的构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

构造方法好几个,下面这个是参数最多的

public ThreadPoolExecutor(	  int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler){
    ...
}

参数说明:

corePoolSize - 线程池核心线程的数量。 (长期维持的数量)
maximumPoolSize - 线程池的最大线程数。
keepAliveTime - 当线程数大于核心时,一旦超过此时间,会将多余的空闲线程杀掉
unit - keepAliveTime 的时间单位。
workQueue - 用来储存等待执行任务的队列。
threadFactory - 线程工厂。
handler - 拒绝策略。可以选择指定的,也可以自定义拒绝策略,实现接口即可

参数详细说明:

corePoolSize:
	是线程池中长期保持的线程数量
maximumPoolSize:
	线程池能够处理的最大线程数量,超过就要执行拒绝策略了(默认为报错)
keepAliveTime:
	当线程池比较闲的时候,超过10s钟的时间可以把最大的线程数中不用的线程减掉,恢复成核心线程数的数量
	
(java中也有优化机制,当任务比较多的时候,会去新创建一些线程 执行任务;比较空闲的时候,会把新创建		的临时线程舍弃掉,最终会保持和核心线程数相等的线程数量)

执行流程:

当有任务的时候,先判断当前是否有空闲线程,有的话则执行

没有空闲流程的话,看是否可存入workQueue等待队列

队列没有满的话添加,加入队列中,排队等待执行

如果队列满了,再看是否超过了 最大的线程数,如果没超过 直接创建线程执行

如果超过了最大线程数,会执行拒绝策略

拒绝策略:

AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 (默认)
DiscardPolicy:也是丢弃任务,但是不抛出异常。
DiscardOldestPolicy:忽略最早的任务(把最早添加任务到队列的任务忽略掉,然后执行当前的任务)
CallerRunsPolicy:把超出的任务交给当前线程执行
				  本来是线程池自己执行的,结果处理不过来就交给当前的主线程处理

演示几种拒绝策略

public static void main(String[] args) {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, //核心线程数
            4, //最大线程数
            10, //空闲等待时间
            TimeUnit.SECONDS, //空闲等待时间的单位
            new LinkedBlockingQueue<>(3), //等待队列的长度
            new ThreadPoolExecutor.AbortPolicy() //拒绝策略,抛异常
    );
    for (int i = 0; i < 8; i++) {
        executor.execute(()->{
            System.out.println(Thread.currentThread().getName());
        });
    }
    
}

执行之后发现:

直接抛出异常了,没有没有抛出异常,说明可能是某个人物执行的比较快

最大线程数 + 队列中的数量 就是该线程池能承受的最大任务量 也就是 7

2.DiscardPolicy 是不会报出任何错的,只做忽略操作

3.DiscardOldestPolicy 执行忽略最早的,也是执行六次

4.CallerRunsPolicy 执行不了的交给主线程处理

main
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-4

自定义拒绝策略:

构造方法中,原本设置拒绝策略的参数,就不要设置了,在设置拒绝参数的位置

设置自定义的拒绝策略,可以使用匿名内部类 ,new RejectedExecutionHandler

public static void main(String[] args) {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,
            4,
            10,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(3),
            new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    System.out.println("自定义拒绝策略执行了...");
                }
            }
    );
    for (int i = 0; i < 8; i++) {
        executor.execute(()->{
            System.out.println(Thread.currentThread().getName());
        });
    }
}

打印结果:

自定义拒绝策略执行了...
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-4
pool-1-thread-2
pool-1-thread-3

推荐阅读