今天看了部分線程池的代碼,對于線程池運(yùn)行機(jī)制又有了一些新的認(rèn)識, 本文是結(jié)合占小狼簡書 加了一些自己的理解和注釋.
該文只是作為個人對于占小狼關(guān)于深入分析java線程池的實(shí)現(xiàn)原理文的自己一些補(bǔ)充薪铜,建議先看他的文章之后再來看該文前方。有理解不對的地方望提出來共同學(xué)習(xí)蒂萎。
1.線程池內(nèi)部狀態(tài):
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; }
AtomicInteger變量ctl的功能非常強(qiáng)大:利用低29位表示線程池中線程數(shù)屯碴,通過高3位表示線程池的運(yùn)行狀態(tài):
1疤祭、RUNNING:-1 << COUNT_BITS票髓,即高3位為111森瘪,該狀態(tài)的線程池會接收新任務(wù)劝萤,并處理阻塞隊(duì)列中的任務(wù)闯狱;
2煞赢、SHUTDOWN: 0 << COUNT_BITS,即高3位為000哄孤,該狀態(tài)的線程池不會接收新任務(wù)照筑,但會處理阻塞隊(duì)列中的任務(wù);
3瘦陈、STOP : 1 << COUNT_BITS凝危,即高3位為001,該狀態(tài)的線程不會接收新任務(wù)晨逝,也不會處理阻塞隊(duì)列中的任務(wù)蛾默,而且會中斷正在運(yùn)行的任務(wù);
4咏花、TIDYING : 2 << COUNT_BITS趴生,即高3位為010阀趴;
5、TERMINATED: 3 << COUNT_BITS苍匆,即高3位為011刘急;
2.任務(wù)執(zhí)行
//得到c從而拿到內(nèi)部狀態(tài)
int c = ctl.get();
//拿到線程池中的線程數(shù),如果線程數(shù)小于核心線程數(shù)浸踩,那么直接創(chuàng)建新的線程執(zhí)行任務(wù)
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//會發(fā)現(xiàn)線程池中很多地方都會重復(fù)的得到內(nèi)部狀態(tài)叔汁,因?yàn)榫€程池主要是用CAS來保證的線程安全性,從而導(dǎo)致如果swap失敗的一些情況需要再次獲得最新的內(nèi)部狀態(tài)用之后做判斷检碗。
//這里如果加入創(chuàng)建新的線程執(zhí)行任務(wù)的時候出現(xiàn)錯誤据块,重新獲取最新的內(nèi)部狀態(tài)用于之后使用
c = ctl.get();
}
//workQueue.offer會跟根據(jù)不同的阻塞隊(duì)列從而導(dǎo)致線程池出現(xiàn)不同的線程添加機(jī)制,我會在2.1中著重說不同阻塞隊(duì)列的情況折剃。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果在我們添加到阻塞隊(duì)列之后另假,狀態(tài)不是RUNNING狀態(tài),會將當(dāng)前任務(wù)從阻塞隊(duì)列移除怕犁,并拒絕這次任務(wù)
if (! isRunning(recheck) && remove(command))
reject(command);
//這種情況是由于corePoolSize允許為0边篮,當(dāng)corePoolSize為0時,第一次會運(yùn)行到這步奏甫,并添加線程到線程池中戈轿。當(dāng)corePoolSize等于0時,會相當(dāng)于只在核心線程池中添加一個線程用于消費(fèi)阻塞隊(duì)列的任務(wù)阵子,這里也會在2.1結(jié)合不同阻塞隊(duì)列說
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
2.1 不同阻塞隊(duì)列對于線程池添加的影響思杯。(這里建議先看過上面推薦的博客,并了解了核心線程池中的workers是什么以及線程池的流程)
workers其實(shí)就是存儲這里的MaxPool中包含的所有的工作線程的一個HashSet
workQueue.offer其實(shí)就是要將任務(wù)添加到阻塞隊(duì)列中挠进,如果能夠添加則返回true并添加到阻塞隊(duì)列中色乾,否則返回false。
addWorker這個步驟其實(shí)就是要向workers中創(chuàng)建一個新的線程用于消費(fèi)阻塞隊(duì)列中的任務(wù)领突。
但是對于不同的阻塞隊(duì)列杈湾,offer的行為是不同的。
LinkedBlockingQueue攘须,當(dāng)我們初始化的時候沒有給他初始容量,那么這里殴泰,他每次offer都可以添加到我們的阻塞隊(duì)列中于宙,因?yàn)長inkedBlockingQueue是基于鏈表結(jié)構(gòu)的無界阻塞隊(duì)列。那么我們?nèi)绻鹀orePoolSize不是0悍汛,則相當(dāng)于只有當(dāng)前workers中只有CorePool捞魁,當(dāng)workerCountOf(c) > corePoolSize的時候,我們只是向阻塞隊(duì)列中添加任務(wù)离咐,供之后線程消費(fèi)谱俭,而不會再添加新的worker到workers了奉件,所以這個時候的MaxPool和CorePool是一樣大的,maxmumPoolSize參數(shù)也就沒有了意義昆著。如果corePoolSize是0县貌,則相當(dāng)于只有一個線程在線程池中,之后的任務(wù)都直接進(jìn)入到阻塞隊(duì)列
LinkedBlockingQueue賦予了初始化容量凑懂,那么我的理解是和ArrayBlockingQueue作用是一樣的煤痕。當(dāng)我們的數(shù)量達(dá)到了核心線程數(shù),接下來會向阻塞隊(duì)列中添加任務(wù)接谨,當(dāng)我們的阻塞隊(duì)列也滿了摆碉。則再創(chuàng)建新的worker加入到workers中,當(dāng)達(dá)到最大線程數(shù)時脓豪,最后會reject巷帝。
當(dāng)我們當(dāng)前的線程池核心線程數(shù)大小小于corePoolSize的時候,每次都會創(chuàng)建新的woker來執(zhí)行扫夜,當(dāng)我們等于核心線程數(shù)的時候楞泼,如果這個時候存在空閑的worker,那么會直接使用空閑的worker執(zhí)行历谍,當(dāng)沒有空閑worker的時候會向阻塞隊(duì)列中添加command
SynchronousQueue现拒,擴(kuò)展先閱讀下http://ifeve.com/java-synchronousqueue/.SynchronouseQueue主要是用來做信號量方面的應(yīng)用的,Excutors.newCachedThreadPool就是用的這種方式從而達(dá)到緩存線程池的作用望侈。
這里的SynchronouseQueue用的很精妙.
offer不是每回都返回false印蔬,而是當(dāng)我們當(dāng)前的可用的worker隊(duì)列中,如果不存在可用的worker的時候才會返回false脱衙,如果存在可用的worker的時候侥猬,會返回 true,這是因?yàn)楫?dāng)我們worker執(zhí)行完當(dāng)前work的任務(wù)的時候捐韩,我們回去在繼續(xù)getTask退唠,發(fā)現(xiàn)task不存在的時候,我們會去調(diào)用workQueue.take或poll,從而阻塞在這里荤胁,當(dāng)我們有可用woker的時候瞧预,相當(dāng)于阻塞隊(duì)列存在一個待消費(fèi)的take,這個時候offer會返回true
因?yàn)橹耙恢睂Σ煌?duì)列對線程池運(yùn)行有什么影響不是很理解仅政,這一部分是我經(jīng)過測試并看了一部分源碼得出的一些理解垢油,有問題希望大家能提出來。