共计 3065 个字符,预计需要花费 8 分钟才能阅读完成。
餐厅的约会
餐盘在灯光的照耀下分外晶莹清白,女朋友拿起红酒杯轻轻地抿了一小口,对我说:“常常听你说线程池,到底线程池到底是个什么原理?”我楞了一下,心里想女朋友明天是怎么了,怎么忽然问出这么业余的问题,但做为一个专业人士在女朋友背后也不能露怯啊,想了一下便说:“我先给你讲讲我前共事老王的故事吧!”
大龄程序员老王
老王是一个曾经北漂十多年的程序员,岁数大了,加班加不过年轻人,升迁也有望,于是拿着手里的一些积蓄,回老家转行守业。他抉择了洗浴行业,开一家洗浴核心,没错,一家正规的洗浴核心。之前在北京的时候,喜爱去的澡堂叫“清华池”,他想了想,就给本人的洗浴核心取名为“线程池”。
线程池洗浴核心
线程池停业当前,老王发现有顾客想做足疗,于是就招聘了 1 个足疗技师,多减少了一项业务减少了支出。随着做足疗的顾客增多,为了赚更多钱又招聘了 4 个足疗技师。
过了一段时间,洗浴核心的生意越来越好,做足疗的顾客也越来越多。然而,老王发现自己店里的足疗技师曾经有 5 个足疗技师,再招聘就太多了,领取不起再多工资了。足疗技师忙不过来怎么办?老王是个聪明人,马上想到方法:让顾客排队,有哪个足疗技师做完了,闲暇进去了,就在队伍里再叫一个顾客持续做。
繁忙的周末
一到周末,来洗浴核心的顾客比平时多了几倍,想足疗的顾客排队工夫太长,顾客们曾经不耐烦了。老王马上做出反馈,又紧急从其余洗浴核心招聘了 5 个足疗技师,为队伍里顾客做足疗,大大减少排队的顾客。
不过,有时生意太火爆了,紧急招聘的技师也用上了,顾客排队工夫也是很长,再来新的顾客,老王只能满脸赔笑地和顾客说:“您下次再来吧,下次给您找个好技师。”,把顾客拒之门外。
过了周末当前,店里不能养闲人啊,老王就把紧急招聘的技师都解雇了。
老王的经营之道
老王的生意越做越红火,很快就要开分店、融资上市、走上人生巅峰。既然这么胜利,就让咱们来复盘一下他的经营之道吧:
如果你理解了老王的经营之道,线程池就不难理解了,把 顾客
替换成 工作
,把 足疗技师
替换成 线程
, 线程池洗浴核心
就是 线程池
了,线程池的外部原理就是这样的:
梦醒
铃铃铃,闹铃把我吵醒,原来是一场梦啊,我哪有什么女朋友?今天上午有一个面试,连忙起床洗漱结束,就登程了。在路上回忆那个奇怪的梦,要不再温习一下线程池的外部原理吧!
先看一下 ThreadPoolExecutor 类的 execute 办法:
public void execute(Runnable command) {if (command == null) | |
throw new NullPointerException(); | |
// 获取 clt,clt 记录着线程池状态和运行线程数。int c = ctl.get(); | |
// 运行线程数小于外围线程数时,创立线程放入线程池中,并且运行当前任务。if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true)) | |
return; | |
// 创立线程失败,从新获取 clt。c = ctl.get();} | |
// 线程池是运行状态并且运行线程大于外围线程数时,把工作放入队列中。if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get(); | |
// 从新查看线程池不是运行状态时,// 把工作移除队列,并通过回绝策略对该工作进行解决。if (! isRunning(recheck) && remove(command)) | |
reject(command); | |
// 以后运行线程数为 0 时,创立线程退出线程池中。else if (workerCountOf(recheck) == 0) | |
addWorker(null, false); | |
} | |
// 运行线程大于外围线程数时并且队列已满时,// 创立线程放入线程池中,并且运行当前任务。else if (!addWorker(command, false)) | |
// 运行线程大于最大线程数时,失败则回绝该工作 | |
reject(command); | |
} |
在 execute 办法中,屡次调用的 addWorker 办法,再看一下这个办法:
private boolean addWorker(Runnable firstTask, boolean core) { | |
retry: | |
for (;;) { | |
// 获取 clt,clt 记录着线程池状态和运行线程数。int c = ctl.get(); | |
// 获取线程池的运行状态。int rs = runStateOf(c); | |
// 线程池处于敞开状态,或者当前任务为 null | |
// 或者队列不为空,则间接返回失败。if (rs >= SHUTDOWN && | |
! (rs == SHUTDOWN && | |
firstTask == null && | |
! workQueue.isEmpty())) | |
return false; | |
for (;;) { | |
// 获取线程池中的线程数 | |
int wc = workerCountOf(c); | |
// 线程数超过 CAPACITY,则返回 false;// 这里的 core 是 addWorker 办法的第二个参数,// 如果为 true 则依据外围线程数进行比拟,// 如果为 false 则依据最大线程数进行比拟。if (wc >= CAPACITY || | |
wc >= (core ? corePoolSize : maximumPoolSize)) | |
return false; | |
// 尝试减少线程数,如果胜利,则跳出第一个 for 循环 | |
if (compareAndIncrementWorkerCount(c)) | |
break retry; | |
// 如果减少线程数失败,则从新获取 ctl | |
c = ctl.get(); | |
// 如果以后的运行状态不等于 rs,阐明状态已被扭转,// 返回第一个 for 循环继续执行 | |
if (runStateOf(c) != rs) | |
continue retry; | |
} | |
} | |
boolean workerStarted = false; | |
boolean workerAdded = false; | |
Worker w = null; | |
try { | |
// 依据当前任务来创立 Worker 对象 | |
w = new Worker(firstTask); | |
final Thread t = w.thread; | |
if (t != null) { | |
final ReentrantLock mainLock = this.mainLock; | |
mainLock.lock(); | |
try { | |
// 取得锁当前,从新查看线程池状态 | |
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(); | |
} | |
if (workerAdded) { | |
// 启动线程,开始运行工作 | |
t.start(); | |
workerStarted = true; | |
} | |
} | |
} finally {if (! workerStarted) | |
addWorkerFailed(w); | |
} | |
return workerStarted; | |
} |
面试
一个衣着格子衬衫的中年男子坐在我背后,对我说:“您好,我是明天的面试官。”我微笑地回应:“您好。”面试官面无表情地问我:“线程池肯定用过吧,能说说线程池的外部原理嘛?”我差点笑出声来,自信满满地说……