多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內(nèi)最全I(xiàn)T社區(qū)平臺 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁 > 互聯(lián)網(wǎng) > 理解ThreadPoolExecutor源碼(二)execute函數(shù)的巧妙設(shè)計和閱讀心得

理解ThreadPoolExecutor源碼(二)execute函數(shù)的巧妙設(shè)計和閱讀心得

來源:程序員人生   發(fā)布時間:2014-09-05 08:43:57 閱讀次數(shù):3121次

ThreadPoolExecutor.execute()源碼提供了大量注釋來解釋該方法的設(shè)計考慮。下面的源碼來自jdk1.6.0_37

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 } }

使用這么多if-else就是為了性能考慮,減小鎖的使用范圍,避免execute方法整個執(zhí)行過程中都持有mainLock鎖。可以看到只有調(diào)用addIfUnderCorePoolSize、ensureQueuedTaskHandled、addIfUnderMaximumPoolSize這3個方法才需要持有鎖。如果新提交的任務(wù),不會進(jìn)入這3個方法,那么就不需要持有鎖。我們來看下,execute方法的設(shè)計是否能夠有效地減少進(jìn)入這3個方法的次數(shù),實現(xiàn)快進(jìn)快出。


第4行代碼poolSize >= corePoolSize,為什么要加這個判斷呢?

如果通過prestartAllCoreThreads事先啟動了所有核心線程,或者是提交的任務(wù)已經(jīng)讓當(dāng)前大小poolSize達(dá)到了核心大小 corePoolSize,并且核心線程不會死亡(allowCoreThreadTimeOut=false不允許核心線程超時退出,并且任務(wù)執(zhí)行過程沒有拋出異常導(dǎo)致線程退出),那么線程池中的線程數(shù)一定不會比corePoolSize小,此后如果再提交新任務(wù),那么就不會進(jìn)入addIfUnderCorePoolSize方法。poolSize >= corePoolSize就是為了減少進(jìn)入addIfUnderCorePoolSize函數(shù)的次數(shù),減少鎖的獲取和釋放次數(shù)。如果poolSize<corePoolSize,那么就會進(jìn)入addIfUnderCorePoolSize,該方法會在加鎖情況下,判斷線程池的狀態(tài)和當(dāng)前大小,然后決定是否需要新加線程來處理任務(wù)。由于addIfUnderCorePoolSize會持有mainLock鎖,所以可以防止其他線程對線程池的并發(fā)修改。也就說可以保證:在不需要添加新線程的時候,就不會添加。The call to addIfUnderCorePoolSize rechecks runState and pool size under lock (they change only under lock) so prevents false alarms that would add threads when it shouldn't。源碼中這段注釋,說的就是這個效果。


第5行代碼if (runState == RUNNING && workQueue.offer(command)),如果程序能走到這行代碼,從任務(wù)提交者的角度來看,此時線程池大小已經(jīng)達(dá)到了核心大小(雖然事實情況不一定如此,因為沒有加鎖,存在并發(fā)修改的可能;而且線程池中的線程也可能會死亡。如果滿足條件:
runState == RUNNING && workQueue.offer(command) 這意味著:線程池仍然處于運(yùn)行狀態(tài),并且任務(wù)排隊已經(jīng)成功。為什么程序執(zhí)行到這里依然不能結(jié)束,還是需要走之后的代碼呢?因為沒有加鎖,判斷條件不一定可靠。考慮下面2個場景:如果任務(wù)剛開始調(diào)用offer(還沒有成功地插入到阻塞隊列中),線程池中的線程全部死亡,那么就沒有線程來處理當(dāng)前提交的這個任務(wù)了;如果任務(wù)剛剛排隊成功,別的線程調(diào)用了shutdownNow()關(guān)閉了線程池,那么按照shutdownNow函數(shù)的語義,這個任務(wù)不應(yīng)該被處理。由于存在這2種特殊情況,所以必須進(jìn)行后續(xù)的處理。but may also fail to add them when they should. This is compensated within the following steps.這就是說:addIfUnderCorePoolSize能夠保證不需要新線程的時候就不添加,但是不能保證需要添加新線程的時候就添加。所以讓任務(wù)排隊成功的時候,需要在鎖的保護(hù)下,判斷是否需要刪除這個任務(wù),或者是否需要新增線程來處理任務(wù)。


第6行代碼if (runState != RUNNING || poolSize == 0),如果任務(wù)成功排隊(workQueue.offer()返回true)后,線程池被關(guān)閉或者沒有存活的線程,那么就需要執(zhí)行ensureQueuedTaskHandled(command),也就是說這種情況下,任務(wù)可能沒有得到合適的處置。如果任務(wù)成功排隊后,線程池仍然處在運(yùn)行狀態(tài),而且有存活的線程,那么就能夠確保該新提交的任務(wù)一定會被處理。為什么會這樣呢?我們知道如果想關(guān)閉ThreadPoolExecutor,只有3種途徑:調(diào)用shutdown方法,調(diào)用shutdownNow方法,線程池最后一個工作者退出(對應(yīng)workerDone方法)。查看源碼我們知道,這3個可能導(dǎo)致線程池關(guān)閉的方法,最終都會調(diào)用tryTerminate()方法。也就是說如果線程池想要終止,就必須通過該方法。

/** * Transitions to TERMINATED state if either (SHUTDOWN and pool * and queue empty) or (STOP and pool empty), otherwise unless * stopped, ensuring that there is at least one live thread to * handle queued tasks. * * This method is called from the three places in which * termination can occur: in workerDone on exit of the last thread * after pool has been shut down, or directly within calls to * shutdown or shutdownNow, if there are no live threads. */ private void tryTerminate() { if (poolSize == 0) { int state = runState; if (state < STOP && !workQueue.isEmpty()) { state = RUNNING; // disable termination check below Thread t = addThread(null); if (t != null) t.start(); } if (state == STOP || state == SHUTDOWN) { runState = TERMINATED; termination.signalAll(); terminated(); } } }

當(dāng)線程池的線程池數(shù)是0的時候,如果線程池是running或者shutdown狀態(tài),并且任務(wù)隊列不為空,那么就會新增1個線程來處理任務(wù);如果線程池是狀態(tài),或者處于shutdown狀態(tài)并且任務(wù)隊列為空,那么線程池就會退出。也就是說,如果任務(wù)排隊成功后,線程池還沒有終止,那么該任務(wù)一定會得到合理的處置。

如果在任務(wù)插入之前或者插入的過程中,線程池不在運(yùn)行狀態(tài)runState != RUNNING 或者沒有存活的線程poolSize == 0,那么就需要自己考慮下:如何處理該提交的任務(wù),這通過ensureQueuedTaskHandled來完成。如果if (runState == RUNNING && workQueue.offer(command))條件是真,那么隨后if (runState != RUNNING || poolSize == 0)基本上很少出現(xiàn),大部分場景下都不需要執(zhí)行ensureQueuedTaskHandled方法,就不需要獲取和釋放鎖。下面我們通過源碼看下,ensureQueuedTaskHandled方法是如何處理異常場景的。

/** * Rechecks state after queuing a task. Called from execute when * pool state has been observed to change after queuing a task. If * the task was queued concurrently with a call to shutdownNow, * and is still present in the queue, this task must be removed * and rejected to preserve shutdownNow guarantees. Otherwise, * this method ensures (unless addThread fails) that there is at * least one live thread to handle this task * @param command the task */ private void ensureQueuedTaskHandled(Runnable command) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); boolean reject = false; Thread t = null; try { int state = runState; if (state != RUNNING && workQueue.remove(command)) reject = true; else if (state < STOP && poolSize < Math.max(corePoolSize, 1) && !workQueue.isEmpty()) t = addThread(null); } finally { mainLock.unlock(); } if (reject) reject(command); else if (t != null) t.start(); }

該方法持有mainLock鎖,所以可以防止線程池的并發(fā)修改。如果線程池不在running狀態(tài)(state != RUNNING ),并且新提交的任務(wù)還駐留在任務(wù)隊列中(workQueue.remove(command)返回true),那么當(dāng)前提交的任務(wù)會被拒絕執(zhí)行(調(diào)用reject(comma)方法)。也就是說,只要線程池不是running狀態(tài),那么就一定會拒絕執(zhí)行當(dāng)前提交的任務(wù),除非該任務(wù)已經(jīng)被線程池中的線程處理了(workQueue.remove返回false)。這里不區(qū)分到底是shutdown()關(guān)閉的線程池,還是通過shutdownNow關(guān)閉的線程池。如果新提交一個任務(wù),并且執(zhí)行流程進(jìn)入了ensureQueuedTaskHandled()函數(shù),那么該任務(wù)可能會被拒絕執(zhí)行,也可能會被正常執(zhí)行。如果線程池是running或者shutdown狀態(tài)(state < STOP),并且線程池中已經(jīng)沒有存活的線程,并且任務(wù)隊列非空,那么就需要新加1個線程,來處理等待執(zhí)行的任務(wù)。


可以看到:通過多次無鎖的條件判斷,能夠有效地減少提交任務(wù)時對mainLock鎖的競爭;也能夠確在并發(fā)執(zhí)行的情況下,當(dāng)前新提交的任務(wù)和線程池中等待執(zhí)行的任務(wù),都能得到合適的處理。不知道大師Doug Lea是如何想到這么巧妙的設(shè)計和實現(xiàn)execute()方法的!雖然execute方法能夠提高性能,但是犧牲了代碼的可讀性和簡易性。對于并發(fā)程序,還是那句經(jīng)典的老話make it right before you make it faster。

 

生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 亚洲国产精品免费在线观看 | 在线观看h视频播放高清 | 男人懂得成a人v网站 | 亚洲视频在线不卡 | 欧美综合视频在线 | 337p日本大胆欧美人术艺术精品 | 国产二区视频在线观看 | 毛片免费在线观看网址 | 午夜影院在线观看免费 | 欧美一区二区激情三区 | 亚洲欧美在线视频免费 | 久久久久免费精品国产 | 欧美另类xxxx图片 | 亚洲视频网址 | 中文字幕福利 | 亚洲精品在线网址 | 在线看毛片网站 | 91精品国产闺蜜国产在线闺蜜 | 国产国产人免费视频成69大陆 | 国产亚洲精品一区二区在线观看 | 俺去久久| 成人在色线视频在线观看免费大全 | 久久久欧美综合久久久久 | 国产欧美一区二区另类精品 | 国产精品麻豆高清在线观看 | 福利片 在线 | 三级在线观看视频 | 成年人在线观看免费视频 | 国产精品96久久久久久久 | 欧美小说图片 | 国产丰满主播丝袜勾搭秀 | 国产免费福利视频一区二区 | 女人天堂网在线观看2019 | 性欧美成人免费观看视 | 欧美高清另类video | 欧美最猛黑人xxxx黑人猛交 | 中文乱码在线观看 | 亚洲欧美日韩人成 | 日韩国产欧美 | 午夜在线播放免费人成无 | 久草综合网 |