java并发编程-线程池的使用
参考文章:http://www.cnblogs.com/dolphin0520/p/3932921.html
深入剖析线程池实现原理
将从下面几个方面讲解:
1.线程池状态
2.任务的执行
3.线程池中的线程初始化
4.任务缓存队列及排队策略
5.任务拒绝策略
6.线程池的关闭
7.线程池容量的动态调整
1.线程池状态
在ThreadPoolExcutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile int runState; static final int RUNNING = 0; static final int SHUTDOWN = 1; static final int STOP = 2; static final int TERMINATED = 3;
runState表示当前线程池的状态,它是一个vlolatile变量用来保证线程之间的可见性;
下面的几个static final变量表示runState可能的几个取值。
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能接受新的任务,他会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会尝试终止正在执行的任务;
当线程池处于SHUTDOWN或者STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
2.任务的执行
在了解将任务提交给线程池到任务执行完毕的整个过程之前,我们先来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:
1 private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务 2 private final ReentrantLock mainLock = new ReentrantLock(); //线程池的主要状态锁,对线程池状态(比如线程池大小 3 //、runState等)的改变都要使用这个锁 4 private final HashSet<Worker> workers = new HashSet<Worker>(); //用来存放工作集 5 6 private volatile long keepAliveTime; //线程存货时间 7 private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间 8 private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列) 9 private volatile int maximumPoolSize; //线程池最大能容忍的线程数 10 11 private volatile int poolSize; //线程池中当前的线程数 12 13 private volatile RejectedExecutionHandler handler; //任务拒绝策略 14 15 private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程 16 17 private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数 18 19 private long completedTaskCount; //用来记录已经执行完毕的任务个数
每个变量的作用都已经标明出来了,这里要重点解释一下corePoolSize、maximumPoolSize、largestPoolSize三个变量。
corePoolSize在很多地方被翻译成核心池大小,其实我的理解这个就是线程池的大小。举个简单的例子:
假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。
因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;
当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;
如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;
然后就将任务也分配给这4个临时工人做;
如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。
当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。
这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。
也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。
不过为了方便理解,在本文后面还是将corePoolSize翻译成核心池大小。
largestPoolSize只是一个用来起记录作用的变量,用来记录线程池中曾经有过的最大线程数目,跟线程池的容量没有任何关系。
下面我们进入正题,看一下任务从提交到最终执行完毕经历了哪些过程。
在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); // is shutdown or saturated } }
上面的代码可能看起来不是那么容易理解,下面我们一句一句解释:
首先,判断提交的任务command是否为null,若是null,则抛出空指针异常;
接着是这句,这句要好好理解一下:
1 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))
由于是或条件运算符,所以先计算前半部分的值,如果线程池中当前线程数不小于核心池大小,那么就会直接进入下面的if语句块了。
如果线程池中当前线程数小于核心池大小,则接着执行后半部分,也就是执行
1 addIfUnderCorePoolSize(command)
如果执行完addIfUnderCorePoolSize这个方法返回false,则继续执行下面的if语句块,否则整个方法就直接执行完毕了。
如果执行完addIfUnderCorePoolSize这个方法返回false,然后接着判断:
if (runState == RUNNING && workQueue.offer(command))
如果当前线程池处于RUNNING状态,则将任务放入任务缓存队列;如果当前线程池不处于RUNNING状态或者任务放入缓存队列失败,则执行:
addIfUnderMaximumPoolSize(command)
如果执行addIfUnderMaximumPoolSize方法失败,则执行reject()方法进行任务拒绝处理。
回到前面:
if (runState == RUNNING && workQueue.offer(command))
这句的执行,如果说当前线程池处于RUNNING状态且将任务放入任务缓存队列成功,则继续进行判断:
if (runState != RUNNING || poolSize == 0)