Java多线程系列 JUC线程池02 线程池原理解析(一)

时间:2021-05-26 06:20:52

转载  http://www.cnblogs.com/skywang12345/p/3509960.html ; http://www.cnblogs.com/skywang12345/p/3509941.html

ThreadPoolExecutor简介

ThreadPoolExecutor是线程池类。对于线程池,可以将它理解为"存放一定数量线程的一个线程集合。线程池允许若个线程同时运行,通过线程池对运行的线程进行管理"。

线程池的生命周期

线程池的5种状态是:RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED。

线程池状态定义代码如下:

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

说明

ctl是一个AtomicInteger类型的原子对象。ctl记录了"线程池中的任务数量"和"线程池状态"2个信息。
ctl共包括32位。其中,高3位表示"线程池状态",低29位表示"线程池中的任务数量"。

RUNNING    -- 对应的高3位值是111。
SHUTDOWN -- 对应的高3位值是000。
STOP -- 对应的高3位值是001。
TIDYING -- 对应的高3位值是010。
TERMINATED -- 对应的高3位值是011。

程池各个状态之间的切换如下图所示:

Java多线程系列   JUC线程池02  线程池原理解析(一)

1. RUNNING

(01) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对阻塞队列中的任务进行处理。
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态!
道理很简单,在ctl的初始化代码中(如下),就将它初始化为RUNNING状态,并且"任务数量"初始化为0。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

2. SHUTDOWN

(01) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能对阻塞队列中的任务进行处理。
(02) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3. STOP

(01) 状态说明:线程池处在STOP状态时,不接收新任务,不处理阻塞队列中的任务,并且尝试中断正在处理的任务。
(02) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4. TIDYING
(01) 状态说明:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(02) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5. TERMINATED
(01) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(02) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

ThreadPoolExecutor数据结构

ThreadPoolExecutor.java中的成员变量定义如下:

private final BlockingQueue<Runnable> workQueue;// 阻塞队列。

private final ReentrantLock mainLock = new ReentrantLock();// 互斥锁

private final HashSet<Worker> workers = new HashSet<Worker>();// 线程集合。一个Worker对应一个线程。

private final Condition termination = mainLock.newCondition();// “终止条件”,与“mainLock”绑定。

private int largestPoolSize;// 线程池中线程数量曾经达到过的最大值。

private long completedTaskCount;// 已完成任务数量

private volatile ThreadFactory threadFactory;// ThreadFactory对象,用于创建线程。

private volatile RejectedExecutionHandler handler;// 拒绝策略的处理句柄。

private volatile long keepAliveTime;// 线程没有任务执行保持的存活时间。

private volatile boolean allowCoreThreadTimeOut;

private volatile int corePoolSize;// 核心池大小

private volatile int maximumPoolSize;// 最大池大小

1. workers
    workers是HashSet<Work>类型,即它是一个Worker集合。一个Worker对应一个线程,也就是说线程池通过workers包含了"一个线程集合"。当Worker对应的线程池启动时,它会执行线程池中的任务;当执行完一个任务后,它会从线程池的阻塞队列中取出一个阻塞的任务来继续运行。

2. workQueue

    workQueue是BlockingQueue 类型,即用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列: 
    (1) ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务; 
    (2) LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene; 
    (3) SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene; 
  (4) priorityBlockingQuene:具有优先级的*阻塞队列;

3. mainLock

mainLock是互斥锁,通过mainLock实现了对线程池的互斥访问。

4. corePoolSize
    线程池中的核心线程数,在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到阻塞队列当中。

5. maximumPoolSize

   线程池最大线程数,它表示在线程池中最多能创建多少个线程 ,例如,当新任务提交给线程池时(通过execute方法) , 如果线程池中运行的线程数量大于maximumPoolSize;则拒绝将要加入的任务,并执行相应的拒绝策略。当阻塞队列是*队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入阻塞队列.

  如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的*值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心池大小和最大池大小的值是在创建线程池设置的;但是,也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。

6. poolSize
    poolSize是当前线程池的实际大小,即线程池中任务的数量。

7. allowCoreThreadTimeOut 
    allowCoreThreadTimeOut表示是否允许"线程在空闲状态时,仍然能够存活";

8. keepAliveTime

 表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

9. unit

 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒

10. threadFactory
    threadFactory是ThreadFactory对象。它是一个线程工厂类,"线程池通过ThreadFactory创建线程 ,并设置一个具有识别度的线程名"。默认为 defaultThreadFactory

11. handler

handler是RejectedExecutionHandler类型。它是"线程池拒绝策略"的句柄,也就是说"当某任务添加到线程池中,而线程池拒绝该任务时,线程池会通过handler进行相应的处理"。
     线程池提供了4种策略:
    (1) AbortPolicy:直接抛出异常,默认策略;
    (2) CallerRunsPolicy:用调用者所在的线程来执行任务;
    (3) DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
    (4) DiscardPolicy:直接丢弃任务;当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义拒绝策略,如记录日志或持久化存储不能处理的任务。

综上所说,线程池通过workers来管理"线程集合",每个线程在启动后,会执行线程池中的任务;当一个任务执行完后,它会从线程池的阻塞队列中取出任务来继续运行。阻塞队列是管理线程池任务的队列,当添加到线程池中的任务超过线程池的容量时,该任务就会进入阻塞队列进行等待。


“泰戈尔说,不要着急,最好的总会在最不经意的时候出现。那我们要做的就是:怀揣希望去努力,静待美好的出现。”