線程池解析第二章-線程池源碼問題總結(jié)

線程池解析第一章-源碼解析
線程池解析第二章-線程池源碼問題總結(jié)

上篇文章分析了關(guān)于線程池在源碼層面對(duì)提交任務(wù)的處理方式,但是光光去看源碼,有些值得思考的地方可能會(huì)被忽略,但是這些點(diǎn)可能是對(duì)線程池理解至關(guān)重要,理解了才能對(duì)線程池有更為整體的認(rèn)識(shí),本篇文章主要是對(duì)閱讀源碼的一個(gè)總結(jié),把一些問題拿出來進(jìn)行一個(gè)總結(jié)

Q1:為什么要使用線程池
A:當(dāng)創(chuàng)建一個(gè)線程和銷毀一個(gè)線程所需要的時(shí)間大于這個(gè)線程的執(zhí)行時(shí)間,那么這就是一個(gè)資源上的浪費(fèi),當(dāng)線程創(chuàng)建過多了以后,線程間的切換則會(huì)消耗大量的內(nèi)存和資源,線程池中保存了空閑的線程,可以利用空閑的線程,避免創(chuàng)建銷毀的資源浪費(fèi)

Q1-0:線程和操作系統(tǒng)及CPU的關(guān)系
A:線程實(shí)際上是操作系統(tǒng)的概念,java本身并不能創(chuàng)建線程,而是調(diào)用操作系統(tǒng)提供的接口,實(shí)現(xiàn)線程的創(chuàng)建,控制,銷毀,實(shí)際創(chuàng)建的是操作系統(tǒng)線程,在經(jīng)由操作系統(tǒng)分配到空閑的CPU上

  • 上面兩個(gè)問題是對(duì)于線程最基本的概念,只有理解了線程的由來,才能對(duì)多線程有更完整的認(rèn)識(shí)

Q2:線程池中的核心線程是如何循環(huán)利用的
A:其實(shí)這個(gè)問題在閱讀完源碼后就很好理解了,當(dāng)執(zhí)行runWorker()方法的時(shí)候,首先會(huì)去獲取任務(wù),然后有一個(gè)while語(yǔ)句去判斷當(dāng)前任務(wù)是否為空,或者調(diào)用getTask()方法去判斷結(jié)果是否為空而對(duì)應(yīng)的getTask方法則是去循環(huán)獲取workqueue阻塞隊(duì)列中的任務(wù),此處實(shí)現(xiàn)了核心線程的循環(huán)利用,只不過此處需要注意對(duì)有,核心線程在默認(rèn)情況下會(huì)一直存在,當(dāng)核心線程在被allowCoreThreadTimeOut為true后,也是可以被銷毀的,但是非核心線程在keepalivetime過期后如果沒有獲取到任務(wù)將會(huì)自動(dòng)被銷毀

Q3:在線程池中,如果線程沒有全部執(zhí)行任務(wù),為什么不是利用閑置的而是創(chuàng)建新的線程呢?
A:這是我之前代碼沒讀完的時(shí)候產(chǎn)生的一個(gè)問題,看完代碼后這個(gè)問題就很簡(jiǎn)單了,對(duì)于核心線程來說,如果隊(duì)列任務(wù)為空,那么這些核心線程就是屬于空閑狀態(tài),所以核心線程是否處于空閑狀態(tài)一個(gè)很關(guān)鍵的所在就是工作隊(duì)列中是否有任務(wù),如果想要利用空閑線程,那么任務(wù)隊(duì)列就必須有任務(wù),但如果此刻線程池線程數(shù)還沒有達(dá)到核心線程數(shù),這時(shí)候則會(huì)創(chuàng)建核心線程,但是當(dāng)線程數(shù)量大于核心線,則會(huì)加入到工作隊(duì)列當(dāng)中,這時(shí)候就成功利用閑置的核心線程,但是不會(huì)增加新的線程

Q4:Set類型的workers又是做什么的,為什么worker會(huì)加入到workers當(dāng)中
A:workers是當(dāng)前線程池中所有線程的集合,當(dāng)有新的線程被創(chuàng)建后,都會(huì)被加入到這個(gè)集合當(dāng)中,對(duì)于workers的操作,只有在拿到mainLock的時(shí)候才能進(jìn)行訪問,在shutdown()等關(guān)閉方法中,被用作檢測(cè)以確保呼叫者可以中斷workers集合中的每一個(gè)工作線程,并對(duì)所有workers中的工作線程進(jìn)行中斷

Q5:在addWorker方法中,當(dāng)任務(wù)加入任務(wù)隊(duì)列中且線程池中可用線程數(shù)量為0時(shí),調(diào)用addWorker(null, false)是什么意思,當(dāng)無法再向任務(wù)隊(duì)列中添加任務(wù)時(shí),調(diào)用addWorker(command, false)的作用是什么,他和addWorker(command, true)方法有什么區(qū)別
A: 調(diào)用addWorker(null, false)的作用,addWorker是為了創(chuàng)建線程處理傳遞的任務(wù),此處傳遞的task為空,說明此處創(chuàng)建了一個(gè)任務(wù)為空的worker對(duì)象,目的只有一個(gè),就是處理之前被加入到任務(wù)隊(duì)列中的task。
addWorker(command, false)和addWorker(command, true)有什么區(qū)別,在addWorker方法中,只是簡(jiǎn)單的做了一個(gè)線程數(shù)量的判斷,但是在getTask方法中,如果是非核心線程,在keepAliveTime內(nèi)沒有獲取到任務(wù),將會(huì)銷毀非核心線程

  • Q2~5主要說的是線程池實(shí)現(xiàn)邏輯上的問題,如有不對(duì)的地方歡迎指正

Q6:在線程池當(dāng)中,submit()/execute()之間關(guān)系及區(qū)別
A:線程池的執(zhí)行入口通常為submit()/execute(),而submit()方法可以返回線程執(zhí)行的結(jié)果,使用execute()方法則不需要返回執(zhí)行的結(jié)果,submit()方法實(shí)際是調(diào)用了ThreadPoolExecutor父類AbstractExecutorService的submit方法,該方法可以接收兩個(gè)類型的參數(shù),一個(gè)是Runnable對(duì)象,一個(gè)是callable對(duì)象,但是在submit()方法中會(huì)調(diào)用newTaskFor()返回一個(gè)...
FutureTask對(duì)象,在構(gòu)造FutureTask對(duì)象的時(shí)候,如果傳入的是Runnable類型,也會(huì)被轉(zhuǎn)換成Callable類型

Q7:在線程池當(dāng)中,runnable和callable的關(guān)系及區(qū)別
A:線程池在接受任務(wù)的時(shí)候,往往接受兩種類型,一種是runnable一種是callable類型,runnable類型需要實(shí)現(xiàn)run方法,而callable類型則需要實(shí)現(xiàn)call方法,在線程池進(jìn)行提交任務(wù)的操作中,通過submit提交是可以事先獲取一個(gè)futureTask對(duì)象的返回值,說明提交到submit中的對(duì)象是實(shí)現(xiàn)了call方法的,但在源碼中submit是可以接受runable對(duì)象,這是因?yàn)閟ubmit在接受到了runnable對(duì)象后,會(huì)將runnable轉(zhuǎn)換成callable對(duì)象,我前面說了,callable對(duì)象是需要實(shí)現(xiàn)call方法的,但是被轉(zhuǎn)換的runnbale只有run方法,這一步實(shí)際上源碼的轉(zhuǎn)換類幫助我們重寫了call方法,在call方法中嵌套了run方法

Q8:線程池執(zhí)行任務(wù)類,如果是通過submit方法提交的,那么如何將計(jì)算的結(jié)果放入futureTask對(duì)象當(dāng)中的
A:在進(jìn)行submit方法進(jìn)行提交任務(wù)的時(shí)候,實(shí)際返回的是一個(gè) RunnableFuture對(duì)象,在線程池調(diào)用run方法的時(shí)候,實(shí)際調(diào)用的是RunnableFuture接口,對(duì)應(yīng)的實(shí)現(xiàn)類是futureTask,在futureTask對(duì)應(yīng)接口RunnableFuture的run方法的實(shí)現(xiàn)方法中,則是調(diào)用了任務(wù)類的call方法,并將獲取到的結(jié)果存入futureTask對(duì)象當(dāng)中,對(duì)于futureTask中的實(shí)現(xiàn)邏輯不是很了解的同志可以參考我之前的文章
Future三重奏第二章:FutureTask源碼解析

  • Q6,Q7,Q8這三個(gè)模塊的問題需要放在一起理解,他不是單獨(dú)的孤零零的問題,而是綜合在一起的,彼此緊密關(guān)聯(lián)的問題點(diǎn),主要說明的是對(duì)于線程池中不同提交方式帶來的兩者之間的差別和實(shí)現(xiàn)結(jié)果上的不同,且其中涉及到futureTask類的理解,如果對(duì)futureTask不是很清楚,那么對(duì)于線程池的實(shí)現(xiàn),在理解上就不能說是特別的通透,建議有興趣的朋友可以看看我之前寫的文章

Q9:內(nèi)部類Worker是繼承實(shí)現(xiàn)了AQS的,目的是什么,體現(xiàn)在什么地方
A:主要從Worker對(duì)創(chuàng)建以及使用上來闡述這個(gè)問題,分為以下幾點(diǎn)
1:當(dāng)線程池創(chuàng)建Worker對(duì)象的時(shí)候,對(duì)state對(duì)默認(rèn)賦值為-1,此處對(duì)目的是為了防止線程在執(zhí)行前被標(biāo)記中斷
2:在線程池調(diào)用關(guān)閉操作的時(shí)候,首先會(huì)獲取線程集,依次取出當(dāng)中每一個(gè)線程,當(dāng)線程還沒有執(zhí)行任務(wù)and xxxx 的時(shí)候,將會(huì)被標(biāo)記打斷,此處注意,如果線程對(duì)state為-1,無法被標(biāo)記為中斷
3:在線程池中執(zhí)行runWorker方法的時(shí)候,首先需要調(diào)用w.unlock()方法,調(diào)用此方法將當(dāng)前線程的state置為0,目的是為了防止線程在被調(diào)用前就被標(biāo)記中斷,調(diào)用此方法后,線程就可以被置為中斷狀態(tài)

Q10:interrupt,interrupted,isinterrupted的關(guān)系和作用
A:在這里提出這個(gè)問題是因?yàn)樵谏厦娴膯栴}中,當(dāng)線程加鎖后,需要判斷當(dāng)前線程是否需要被中斷,在一系列當(dāng)判斷后,將會(huì)對(duì)線程進(jìn)行中斷操作,所以此處單獨(dú)提出來看
1:interrupt的作用是對(duì)當(dāng)前線程進(jìn)行中斷,只能作用在當(dāng)前線程
2:interrupted的作用是返回當(dāng)前線程的中斷狀態(tài),并清除中斷位
3:isInterrupted的作用就是返回當(dāng)前線程的中斷狀態(tài),不做任何操作

Q11:對(duì)于線程池當(dāng)中執(zhí)行拒絕策略的解析
A:當(dāng)線程池中任務(wù)隊(duì)列數(shù)量已滿且線程數(shù)量超過線程池最大數(shù)量的時(shí)候,將會(huì)執(zhí)行拒絕策略,拒絕策略有四類,具體執(zhí)行何種拒絕策略需要視創(chuàng)建線程池時(shí)選擇的拒絕策略而定,四種拒絕策略會(huì)采用四種不同的處理方式,且都為ThreadPoolExecutor內(nèi)部類,實(shí)現(xiàn)了接口RejectedExecutionHandler中的執(zhí)行方法,建議此處查看相關(guān)源碼
1:AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。
2:DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。
3:DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程),通過e.getQueue().poll()實(shí)現(xiàn)拒絕策略
4:CallerRunsPolicy:由調(diào)用線程處理該任務(wù),相關(guān)代碼:r.run();直接調(diào)用線程處理

諸王少年時(shí)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容