部分资料来自网络
https://www.cnblogs.com/fixzd/p/9253203.html
ThreadPoolExecutor源码剖析_threadpoolexecutor源码分析_niulx111的博客-优快云博客
设计
ThreadPoolExecutor中存在内部类Worker,线程池通过CAS控制Worker数量, HashSet<Worker>保存这些Worker对象。Worker类实现Runnable接口,Worker对象就是实际的任务执行者,同时也代表一个独立Thread线程对象。Worker=Thread, 每一个Worker对象在实例化时通过ThreadFactory创建一个自己专属的Thread线程对象。这个专属线程start启动后执行这个Worker对象的run方法。Worker的run方法执行从BlockingQueue<Runnable>中取得任务,并执行任务runnable.run方法。通过Queue实现业务线程到线程池线程的切换,线程池工作线程的挂起与唤醒依靠BlockingQueue完成。
BlockingQueue阻塞队列
ArrayBlockingQueue:数组实现的有界队列。生产者消费者共用一把锁。
LinkedBlockingQueue:链表实现的有/无界队列。生产者消费者分别使用两把锁。性能好于ArrayBlockingQueue。
DelayQueue:延迟队列。
PriorityBlockingQueue:优先级队列。
注意
1 、不要用无限队列,应为可能造成大量任务堆积,内存无法释放
2 、死锁。例如:核心线程10,队列100。全部核心线程都执行任务A,A等待任务B执行唤醒,而任务B全部在队列中。此时就会死锁。
任务投递流程
投递核心线程---->放入队列---->创建非核心线程---->拒绝策略
状态
状态由整形变量表示,由大到小依次是:
RUNNING:接受新的任务,处理队列任务;
SHUTDOWN:不在接受新的任务,处理队列任务;
STOP:不在接受新任务,不处理队列任务,中断正在执行的任务线程。shutDownNow();
TIDYING:所有的任务已经结束,任务线程为0,线程池转换到TIDYING;
TERMINATED:停止,线程池已结束,且无任务在执行,即terminated()方法执行完。
submit方法执行逻辑
任务提交到线程池后先交给核心线程,若无空闲则放入队列中,若队列已满则尝试增加线程数到最大值。若以上都不能成功,则启动拒绝策略。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* 3步操作
*
* 1. 如果当前运行的线程数<核心线程数,创建一个新的线程执行任务,调用addWorker方法原子性地检查
* 运行状态和线程数,通过返回false防止不需要的时候添加线程
* 2. 如果一个任务能够成功的入队,仍然需要双重检查,因为我们添加了一个线程(有可能这个线程在上次检查后就已经死亡了)
* 或者进入此方法的时候调用了shutdown,所以需要重新检查线程池的状态,如果必要的话,当停止的时候要回滚入队操作,
* 或者当线程池为空的话创建一个新的线程
* 3. 如果不能入队,尝试着开启一个新的线程,如果开启失败,说明线程池已经是shutdown状态或饱和了,所以拒绝执行该任务
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //工作线程线程数少于核心数
if (addWorker(command, true)) //增加工作线程到HashSet<Worker>
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //任务放入队列
int recheck = ctl.get(); //二次检查
if (! isRunning(recheck) && remove(command)) //线程池停止且移除队列失败
reject(command); //拒绝
else if (workerCountOf(recheck) == 0) //已经无工作线程
addWorker(null, false); //增加工作线程到HashSet<Worker>
}
else if (!addWorker(command, false)) //增加工作线程到HashSet<Worker>
reject(command); //失败 拒绝
}
addWorker方法
添加工作线程。
第一步: 判断线程池状态,for+cas 增加线程个数;
第二步: 在第一步成功后实例化Workder(Thread,Runable),持锁将新workder放入Set中,启动workder中线程t.start()。
第一部分主要是两个循环,外层循环主要是判断线程池状态。内层循环判断当前线程数量,CAS增加数量。
第二部分说明CAS成功了也就是说线程个数加一了,但是现在任务还没开始执行,这里使用全局的独占锁来控制Hashset<Worker> workers里面添加任务,其实也可以使用并发安全的set,但是性能没有独占锁好(这个从注释中知道的)。这里需要注意的是要在获取锁后重新检查线程池的状态(双重检查),这是因为其他线程可能在本方法获取锁前改变了线程池的状态,比如调用了shutdown方法。添加成功则启动任务执行。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 检查当前线程池状态是否是SHUTDOWN、STOP、TIDYING或者TERMINATED
// 且!(当前状态为SHUTDOWN、且传入的任务为null,且队列不为null)
// 条件都成立则返回false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//循环
for (;;) {
int wc = workerCountOf(c);
//如果当前的线程数量超过最大容量或者大于(根据传入的core决定是核心线程数还是最大线程数)核心线程数 || 最大线程数,则返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS增加c,成功则跳出retry
if (compareAndIncrementWorkerCount(c))
break retry;
//CAS失败执行下面方法,查看当前线程数是否变化,变化则继续retry循环,没变化则继续内部循环
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
}
}
//CAS更新线程数成功后创建一个线程worker
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//新建一个线程
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//重新检查线程池状态
//避免ThreadFactory退出故障或者在锁获取前线程池被关闭
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // 先检查线程是否是可启动的
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//判断worker是否添加成功,成功则启动线程,然后将workerStarted设置为true
if (workerAdded) {
//启动线程worker.thread
t.start();
workerStarted = true;
}
}
} finally {
//判断线程有没有启动成功,没有则调用addWorkerFailed方法
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker对象
每个Worker对象持有一个私有Thread对象。Worker是定义在ThreadPoolExecutor中的finnal类,其中继承了AbstractQueuedSynchronizer类和实现Runnable接口,其中的run方法如下。线程启动时调用了其run方法。
public void run() {
runWorker(this);
}
runWorker方法
worker.Thread会循环从queue中取得任务执行其run()方法。
worker线程终止:
1 若执行任务代码时捕获异常则向上抛出,终止循环。
2 getTask()返回null,终止while循环。既队列中无任务或线程总数超标。
最终进入finally{}执行worker.Thread退出,当前worker.Thread不再执行循环,自然终止。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread(); //workerThread
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
//循环获取任务,getTask()返回null时不再循环,线程自然终止。
//getTask()从Queue中取任务,会被阻塞
while (task != null || (task = getTask()) != null) {
w.lock();
// 当线程池是处于STOP状态或者TIDYING、TERMINATED状态时,设置当前线程处于中断状态。防止线程池调用了shutdownnow方法必须中断所有的线程
// 如果不是,当前线程就处于RUNNING或者SHUTDOWN状态,确保当前线程不处于中断状态
// 重新检查当前线程池的状态是否大于等于STOP状态
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
//当前线程打中断标志
wt.interrupt();
try {
//提供给继承类使用做一些统计之类的事情,在线程运行前调用
beforeExecute(wt, task);
Throwable thrown = null;
try {
//执行任务
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//提供给继承类使用做一些统计之类的事情,在线程运行之后调用
afterExecute(task, thrown);
}
} finally {
task = null;
//统计当前worker完成了多少个任务
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//当前线程不再执行while循环或throw Exception时,会执行线程退出操作
//workers中清除当前worker,统计整个线程池完成的任务个数之类的工作
//最终当前线程不再循环,代码走到尽头自然终止。
processWorkerExit(w, completedAbruptly);
}
}
getTask方法
从队列中取得任务,可以通过阻塞队列控制线程的挂起/唤醒。配置了keepAliveTime则queue取任务时等待keepAliveTime时间,没有配置则非阻塞从queue取任务。
队列中无任务或线程总数超标时返回null。返回null代码就会退出while循环,执行runWorker()中的processWorkerExit(),当前线程生命走到尽头。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
//循环
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//线程线程池状态和队列是否为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
//减线程数
decrementWorkerCount();
return null;
}
//线程数量
int wc = workerCountOf(c);
//判断当前线程在阻塞队列获取任务时,是否需要定时唤醒
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//(当前线程数是否大于最大线程数或者)
//且(线程数大于1或者任务队列为空)
//这里有个问题(timed && timedOut)timedOut = false,好像(timed && timedOut)一直都是false吧
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null; //CAS减少Worker数,返回null。当前线程会自然终止。
continue;
}
try {
//获取任务 阻塞线程
Runnable r = timed ?
//如果timed==true,线程阻塞等待一定时间
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//否则 阻塞取任务
workQueue.take();
if (r != null)
return r;
//超时没有取到任务,继续for
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
关闭线程池
可以通过调用线程池的shutdown()或shutdownNow()方法来关闭线程池。他们的原理是遍历线程池中的工作线程,然后遍历Worker线程调用interrupt方法来中断,所以代码中没有处理interrupt中断状态的任务可能永远无法停止。只要调用了这两个关闭方法的一个,isShutdown()就会返回true。当所有的任务都关闭后,才表示线程池关闭成功,这是调用isTerminated()方法会返回true。shutdown方法只是发出了停止信号,等所有任务执行完毕会关闭线程池;而shutdownNow则是立即停止所有任务,返回未执行任务List<Runable>(但是已执行未完成被终止的任务不包含),调用isTerminated()方法会返回true。
isTerminated当调用shutdown()方法后,并且所有提交的任务完成后返回为true;
isTerminated当调用shutdownNow()方法后,成功停止后返回为true;
线程数配置
分布式时要考虑多机生产消费能力和通讯消耗。
最低线程数 : 依据生产者的每秒平均生产能力、单一线程每秒的平均消费能力。
最高线程数 : 依据生产者的每秒可能的峰值生产能力 、单一线程每秒的可能的最低/平均消费能力。
缓冲线程数: 依据情况而定
1) CPU密集型任务的线程池配置
因为cpu执行速度非常快,往往切换线程上下文环境所耗费的时间比执行代码花费的时间更长,所以CPU密集型任务的线程数应该尽量保持和CPU核数一致,以减少线程切换带来的性能损耗。
2) IO密集型任务的线程池配置
IO密集型任务的主要性能瓶颈在于等待IO结果,而CPU利用率比较低,当遇到任务中存在从DB中读取数据、从缓存中读取数据时、远程通信等,就可以认为是IO密集型任务。一般而言业务系统配置线程池基本上都会采用上边的模型配置。