JUC学习系列二(线程池Executors与ThreadPoolExecutor)

概念说明

 《JUC学习系列二(线程池Executors与ThreadPoolExecutor)》

Executor:一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command),

ExecutorService:
是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法
AbstractExecutorService:ExecutorService执行方法的默认实现

ScheduledExecutorService:一个可定时调度任务的接口

ScheduledThreadPoolExecutor:ScheduledExecutorService的实现,一个可定时调度任务的线程池

ThreadPoolExecutor:
线程池,可以通过调用Executors以下静态工厂方法来创建线程池
并返回一个ExecutorService对象

Executor是用来执行提交的Runnable任务的对象,并以接口的形式定义,提供一种提交任务(submission task)与执行任务(run task)之间的解耦方式,还包含有线程使用与周期调度的详细细节等。

ExecutorService接口扩展了Executor,提供管理线程池终止的一组方法,还提供了产生Future的方法,Future是用于追踪一个或多个异步任务的对象,并能返回异步任务的计算结果。

一、ThreadPoolExecutor

简介:ThreadExecutorPool是使用最多的线程池组件,了解它的原始资料最好是从从设计者(Doug Lea)的口中知道它的来龙去脉。在Jdk1.6中,ThreadPoolExecutor直接继承了AbstractExecutorService, 并层级实现了ExecutorService和Executor接口。

线程池处理了两个问题:1.改善了性能。当执行大量的异步任务时,减少了每次调用的开销。2.提供了一直资源管理的方式。线程池内部的线程可以得到有效的复用。每个ThreadPoolExecutor内部还维护了一些基础的统计量,如完成的任务总数.ThreadPoolExecutor在很多场景下都有其用武之地,它提供了很多可调整的参数与可扩展的钩子方法

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数说明:   

corePoolSize                         核心线程池大小
maximumPoolSize                 线程池最大容量大小
keepAliveTime                     线程池空闲时,线程存活的时间


TimeUnit                              时间单位
ThreadFactory                     线程工厂
BlockingQueue                    任务队列
RejectedExecutionHandler  线程拒绝策略

其中比较容易让人误解的是:corePoolSize,maximumPoolSize,workQueue之间关系。

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。

2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行

3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务

4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理

5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程

6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

  ThreadPoolExecutor会自动调节
池子里线程的大小。       通过execute方法提交新任务时,如果当前的池子里线程的大小小于核心线程corePoolSize时,则会创建新线程来处理新的请求,即使当前的工作者线程是空闲的。如果运行的线程数是大于corePoolSize但小于maximumPoolSize,而且当任务队列已经满了时,则会创建新线程。通过设置corePoolSize等于maximumPoolSize,便可以创建固定大小的线程池数量。而设置maximumPoolSize为一个不受限制的数量如Integer.MAX,便可以创建一个适应任意数量大的并发任务的线程池。常规情况下,可以根据构造方法来设置corePoolSize与maximumPoolSize,但也可以通过setCorePoolSize和setMaximumPoolSize方法动态的修改其值。

阻塞队列

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。

LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。

明确拒绝策略

ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 (默认)

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

说明:Executors 各个方法的弊端:

1)newFixedThreadPool 和 newSingleThreadExecutor:

主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。

2)newCachedThreadPool 和 newScheduledThreadPool:

主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

    

二、Executors工具类

最近阿里发布的 Java开发手册中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。主要是这个工具类提供的工厂方法,使用的都是默认参数,虽然可以通过一些手段进行修改,但是对于新手来说,还是存在风险。

Executors利用工厂模式向我们提供了4种线程池实现方式:

// 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
// 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
 
//创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。
//如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。
//此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    } 

// 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
// 那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
// 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    } 

//创建一个线程池,它会维持corePoolSize数目个线程,就算线程空闲,也不会被移除。可以周期地或者延时执行任务
//可以用来做定时任务,心跳检测等功能
 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }	
	

三、线程调度原理


    原文作者:JUC
    原文地址: https://blog.csdn.net/qq_38872310/article/details/80846526
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞