Java多线程系列 JUC线程池02 线程池原理解析(一)
转载 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。
程池各个状态之间的切换如下图所示:
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来管理"线程集合",每个线程在启动后,会执行线程池中的任务;当一个任务执行完后,它会从线程池的阻塞队列中取出任务来继续运行。阻塞队列是管理线程池任务的队列,当添加到线程池中的任务超过线程池的容量时,该任务就会进入阻塞队列进行等待。
“泰戈尔说,不要着急,最好的总会在最不经意的时候出现。那我们要做的就是:怀揣希望去努力,静待美好的出现。”