小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

必須要理清的Java線程池

 Bladexu的文庫(kù) 2019-03-10
作者:騎小豬看流星鏈接:https://www.jianshu.com/p/50fffbf21b39

1、前言

本篇文章主要介紹的是Java(Javaee和Android開發(fā)都會(huì)涉及)中的線程池。線程池不僅是Java多線程編程的重要基礎(chǔ),而且也是Android面試和Javaee面試中,面試官心血來潮突然向你發(fā)難的一道面試題(可能他自己也說不清楚道不明白線程池的概念和應(yīng)用場(chǎng)景,但他們就是想見你一臉尷尬的表情以此作為壓低你工資的籌碼)。說到線程池之前,我們首先必須理清楚 線程、并行和并發(fā)、多線程的基本概念。

線程池的概念大致理清楚之后,我們?cè)诜治鯫kHttp這一經(jīng)典網(wǎng)絡(luò)框架內(nèi)部的線程池是如何實(shí)現(xiàn)的,通過分析源碼進(jìn)一步加深線程池概念。文章一如既往原來的風(fēng)格,篇幅較長(zhǎng)但力爭(zhēng)寫得詳細(xì)明白。筆者從開始寫到發(fā)布花了自己近一周的業(yè)余時(shí)間。(現(xiàn)已經(jīng)對(duì)部分內(nèi)容已進(jìn)行了刪減,初版達(dá)到了萬字)個(gè)人希望看到這篇文章的開發(fā)者可以耐心、仔細(xì)、慢慢的看完,分段閱讀也是較好的選擇。

所謂:九層之臺(tái)、起于壘土 。

另外對(duì)本文有任何意見或者覺得文章內(nèi)容有所不足請(qǐng)?jiān)谠u(píng)論區(qū)直接提出issue,謝謝。

線程:

說起線程,大家可能都不陌生。線程,是程序執(zhí)行的最小單元。一個(gè)標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針,寄存器集合和堆棧組成。另外,線程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。

一個(gè)線程可以創(chuàng)建和撤消另一個(gè)線程,同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。以上概念來自于百度百科。對(duì)于開發(fā)者來說,線程就是幫我們干實(shí)事的伙伴。在Java中,對(duì)于線程的基本操作,我們知道在代碼中有以下三種寫法:

(1)自定義一個(gè)類,去繼承Thread類,重寫run方法

(2)自定義一個(gè)類,去實(shí)現(xiàn)Runnable接口,重寫run方法

(3)自定義一個(gè)類,實(shí)現(xiàn)Callable接口,重寫call方法。

關(guān)于這個(gè)Callable,要多提一嘴,首先,Callable規(guī)定的方法是call(),而Runnable規(guī)定的方法是run();

其次,Callable的任務(wù)執(zhí)行后可返回值,而Runnable的任務(wù)是不能返回值的;然后,call()方法可拋出異常,而run()方法是不能拋出異常的;最后,運(yùn)行Callable任務(wù)可拿到一個(gè)Future對(duì)象。

必須要理清的Java線程池

因?yàn)榫€程的基本概念和使用大家基本上都很熟悉,所以這里就點(diǎn)到為止。

關(guān)于線程,就不得不提及另外一個(gè)經(jīng)常容易被混淆的概念,那就是并行和并發(fā)。

關(guān)于并行和并發(fā),我在網(wǎng)上找了一段關(guān)于并行和并發(fā)的英文資料:

Concurrency is when two tasks can start, run, and complete in overlapping time periods. Parallelism is when tasks literally run at the same time, eg. on a multi-core processor.Concurrency is the composition of independently executing processes, while parallelism is the simultaneous execution of (possibly related) computations.Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.An application can be concurrent – but not parallel, which means that it processes more than one task at the same time, but no two tasks are executing at same time instant.An application can be parallel – but not concurrent, which means that it processes multiple sub-tasks of a task in multi-core CPU at same time.An application can be neither parallel – nor concurrent, which means that it processes all tasks one at a time, sequentially.An application can be both parallel – and concurrent, which means that it processes multiple tasks concurrently in multi-core CPU at same time.

翻譯過來就是:

并發(fā)是兩個(gè)任務(wù)可以在重疊的時(shí)間段內(nèi)啟動(dòng),運(yùn)行和完成。并行是任務(wù)在同一時(shí)間運(yùn)行,例如,在多核處理器上。并發(fā)是獨(dú)立執(zhí)行過程的組合,而并行是同時(shí)執(zhí)行(可能相關(guān)的)計(jì)算。并發(fā)是一次處理很多事情,并行是同時(shí)做很多事情。應(yīng)用程序可以是并發(fā)的,但不是并行的,這意味著它可以同時(shí)處理多個(gè)任務(wù),但是沒有兩個(gè)任務(wù)在同一時(shí)刻執(zhí)行。應(yīng)用程序可以是并行的,但不是并發(fā)的,這意味著它同時(shí)處理多核CPU中的任務(wù)的多個(gè)子任務(wù)。

一個(gè)應(yīng)用程序可以即不是并行的,也不是并發(fā)的,這意味著它一次一個(gè)地處理所有任務(wù)。

應(yīng)用程序可以即是并行的也是并發(fā)的,這意味著它同時(shí)在多核CPU中同時(shí)處理多個(gè)任務(wù)。

看完了這些,可能還是懵懵懂懂。為了徹底理清并行和并發(fā)的概念,我找了兩幅不錯(cuò)的資料圖來幫助我們鞏固理解區(qū)分并行和并發(fā)。

首先是并發(fā):

必須要理清的Java線程池

并發(fā)( Concurrency )

Concurrency,是并發(fā)的意思。并發(fā)的實(shí)質(zhì)是一個(gè)物理CPU(也可以多個(gè)物理CPU) 在若干道程序(或線程)之間多路復(fù)用,并發(fā)性是對(duì)有限物理資源強(qiáng)制行使多用戶共享以提高效率。

從微觀角度來講:所有的并發(fā)處理都有排隊(duì)等候,喚醒,執(zhí)行等這樣的步驟,在微觀上他們都是序列被處理的,如果是同一時(shí)刻到達(dá)的請(qǐng)求(或線程)也會(huì)根據(jù)優(yōu)先級(jí)的不同,而先后進(jìn)入隊(duì)列排隊(duì)等候執(zhí)行。

從宏觀角度來講:多個(gè)幾乎同時(shí)到達(dá)的請(qǐng)求(或線程)在宏觀上看就像是同時(shí)在被處理。

通俗點(diǎn)講,并發(fā)就是只有一個(gè)CPU資源,程序(或線程)之間要競(jìng)爭(zhēng)得到執(zhí)行機(jī)會(huì)。圖中的第一個(gè)階段,在A執(zhí)行的過程中B,C不會(huì)執(zhí)行,因?yàn)檫@段時(shí)間內(nèi)這個(gè)CPU資源被A競(jìng)爭(zhēng)到了,同理,第二個(gè)階段只有B在執(zhí)行,第三個(gè)階段只有C在執(zhí)行。其實(shí),并發(fā)過程中,A,B,C并不是同時(shí)在進(jìn)行的(微觀角度)。但又是同時(shí)進(jìn)行的(宏觀角度)。

簡(jiǎn)單說完了并發(fā),我們繼續(xù)分析并行:

必須要理清的Java線程池

并行(Parallelism)

Parallelism,翻譯過來即并行,指兩個(gè)或兩個(gè)以上事件(或線程)在同一時(shí)刻發(fā)生,是真正意義上的不同事件或線程在同一時(shí)刻,在不同CPU資源上(多核),同時(shí)執(zhí)行。

并行,不存在像并發(fā)那樣競(jìng)爭(zhēng),等待的概念。

圖中,A,B,C都在同時(shí)運(yùn)行(微觀,宏觀)。

關(guān)于并行和并發(fā)的基本概念大概介紹到這里,如果還覺得不是很好理解,請(qǐng)自行谷歌或者百度查閱資料慢慢消化。

上面簡(jiǎn)單回顧了下線程、并行和并發(fā)的基本概念,下面我們?cè)谡f說多線程。

首先,什么是多線程?

我們知道,一個(gè)任務(wù)就是一個(gè)線程 ,但實(shí)際上,一個(gè)應(yīng)用程序?yàn)榱送瑫r(shí)執(zhí)行多個(gè)任務(wù)提供運(yùn)行效率,一般會(huì)涉及到一個(gè)線程以上的數(shù)量。如果,一個(gè)應(yīng)用程序有一個(gè)以上的線程,我們把這種情況就稱之為多線程。

本質(zhì)來說,多線程是為了使得多個(gè)線程完成多項(xiàng)任務(wù),以提高系統(tǒng)的效率。目前為止我們使用多線程應(yīng)用程序的目的是盡可能多地使用計(jì)算機(jī)處理器資源(本質(zhì)是為了讓效率最大化)。

所以,看起來我們僅需要為每個(gè)獨(dú)立的任務(wù)分配一個(gè)不同的線程,并讓處理器確定在任何時(shí)間它總會(huì)處理其中的某一個(gè)任務(wù)。但是,這樣就會(huì)出現(xiàn)一些問題,對(duì)小系統(tǒng)來說這樣做很好。但是當(dāng)系統(tǒng)越來越復(fù)雜時(shí),線程的數(shù)量也會(huì)越來越多,操作系統(tǒng)將會(huì)花費(fèi)更多時(shí)間去理清線程之間的關(guān)系。為了讓我們的程序具備可擴(kuò)展性,我們將不得不對(duì)線程進(jìn)行一些有效的控制。

針對(duì)這種情況,開發(fā)者通過使用線程池就可以有效規(guī)避上述風(fēng)險(xiǎn)。

什么是線程池?

線程池是指在初始化一個(gè)多線程應(yīng)用程序過程中創(chuàng)建一個(gè)線程集合,然后在需要執(zhí)行新的任務(wù)時(shí)重用這些線程而不是新建一個(gè)線程(提高線程復(fù)用,減少性能開銷)。線程池中線程的數(shù)量通常完全取決于可用內(nèi)存數(shù)量和應(yīng)用程序的需求。然而,增加可用線程數(shù)量是可能的。線程池中的每個(gè)線程都有被分配一個(gè)任務(wù),一旦任務(wù)已經(jīng)完成了,線程回到池子中然后等待下一次分配任務(wù)。

為什么要用線程池:

基于以下幾個(gè)原因在多線程應(yīng)用程序中使用線程池是必須的:

1. 線程池改進(jìn)了一個(gè)應(yīng)用程序的響應(yīng)時(shí)間。由于線程池中的線程已經(jīng)準(zhǔn)備好且等待被分配任務(wù),應(yīng)用程序可以直接拿來使用而不用新建一個(gè)線程。

2. 線程池節(jié)省了CLR 為每個(gè)短生存周期任務(wù)創(chuàng)建一個(gè)完整的線程的開銷并可以在任務(wù)完成后回收資源。

3. 線程池根據(jù)當(dāng)前在系統(tǒng)中運(yùn)行的進(jìn)程來優(yōu)化線程時(shí)間片。

4. 線程池允許我們開啟多個(gè)任務(wù)而不用為每個(gè)線程設(shè)置屬性。

5. 線程池允許我們?yōu)檎趫?zhí)行的任務(wù)的程序參數(shù)傳遞一個(gè)包含狀態(tài)信息的對(duì)象引用。

6. 線程池可以用來解決處理一個(gè)特定請(qǐng)求最大線程數(shù)量限制問題。

本質(zhì)上來講,我們使用線程池主要就是為了減少了創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù);節(jié)約應(yīng)用內(nèi)存(線程開的越多,消耗的內(nèi)存也就越大,最后死機(jī))

線程池的作用:

線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量。根據(jù)系統(tǒng)的環(huán)境情況,可以自動(dòng)或手動(dòng)設(shè)置線程數(shù)量,達(dá)到運(yùn)行的最佳效果;少了浪費(fèi)了系統(tǒng)資源,多了造成系統(tǒng)擁擠效率不高。用線程池控制線程數(shù)量,其他線程排隊(duì)等候。

一個(gè)任務(wù)執(zhí)行完畢,再?gòu)年?duì)列的中取最前面的任務(wù)開始執(zhí)行。若隊(duì)列中沒有等待進(jìn)程,線程池的這一資源處于等待。當(dāng)一個(gè)新任務(wù)需要運(yùn)行時(shí),如果線程池中有等待的工作線程,就可以開始運(yùn)行了;否則進(jìn)入等待隊(duì)列。

說完了線程池的概念和作用,我們?cè)倏纯创a中的線程池:

在Java中,線程池的代碼起源之Executor(翻譯過來就是執(zhí)行者)注意:這個(gè)類是一個(gè)接口。

必須要理清的Java線程池

但是嚴(yán)格意義上講Executor并不是一個(gè)線程池(如圖其源碼就一個(gè) execute 方法),所以Executor僅只是一個(gè)執(zhí)行線程的工具。那么,線程池的真正面紗是什么?利用AS的類繼承關(guān)系發(fā)現(xiàn),Executor有一個(gè) ExecutorService 子接口。

必須要理清的Java線程池

實(shí)際上,一般說線程池接口,基本上說的是這個(gè) ExecutorService。ExecutorService源碼里面有各種API(比如說執(zhí)行 excute ( xxx ),比如關(guān)閉 isShutdown ( ))幫助我們?nèi)ナ褂?。ExecutorService接口的默認(rèn)實(shí)現(xiàn)類為ThreadPoolExecutor(翻譯過來就是線程池執(zhí)行者)。既然是默認(rèn)實(shí)現(xiàn)類我們就可以根據(jù)應(yīng)用場(chǎng)景去私人訂制了。

必須要理清的Java線程池

必須要理清的Java線程池

既然找到了突破口,那我們集中火力先去了解下ThreadPoolExecutor

必須要理清的Java線程池

ThreadPoolExecutor構(gòu)造參數(shù)

首先從ThreadPoolExecutor 的構(gòu)造參數(shù)開始分析,通過代碼截圖得知,ThreadPoolExecutor的構(gòu)造方法有以下4種:

必須要理清的Java線程池

下面就構(gòu)造方法里面的參數(shù)逐一分析說明:

1:int corePoolSize (core:核心的) = > 該線程池中核心線程數(shù)最大值

什么是核心線程:線程池新建線程的時(shí)候,如果當(dāng)前線程總數(shù)小于 corePoolSize ,則新建的是核心線程;如果超過corePoolSize,則新建的是非核心線程。

核心線程默認(rèn)情況下會(huì)一直存活在線程池中,即使這個(gè)核心線程啥也不干(閑置狀態(tài))。

如果指定ThreadPoolExecutor的 allowCoreThreadTimeOut 這個(gè)屬性為true,那么核心線程如果不干活(閑置狀態(tài))的話,超過一定時(shí)間( keepAliveTime),就會(huì)被銷毀掉

2:int maximumPoolSize = > 該線程池中線程總數(shù)的最大值

線程總數(shù)計(jì)算公式 = 核心線程數(shù) + 非核心線程數(shù)。

3:long keepAliveTime = > 該線程池中非核心線程閑置超時(shí)時(shí)長(zhǎng)

注意:一個(gè)非核心線程,如果不干活(閑置狀態(tài))的時(shí)長(zhǎng),超過這個(gè)參數(shù)所設(shè)定的時(shí)長(zhǎng),就會(huì)被銷毀掉。但是,如果設(shè)置了 allowCoreThreadTimeOut = true,則會(huì)作用于核心線程。

4:TimeUnit unit = > (時(shí)間單位)

首先,TimeUnit是一個(gè)枚舉類型,翻譯過來就是時(shí)間單位,我們最常用的時(shí)間單位包括:

MILLISECONDS : 1毫秒 、SECONDS : 秒、MINUTES : 分、HOURS : 小時(shí)、DAYS : 天

5:BlockingQueue<Runnable> workQueue = >( Blocking:阻塞的,queue:隊(duì)列)

該線程池中的任務(wù)隊(duì)列:維護(hù)著等待執(zhí)行的Runnable對(duì)象。當(dāng)所有的核心線程都在干活時(shí),新添加的任務(wù)會(huì)被添加到這個(gè)隊(duì)列中等待處理,如果隊(duì)列滿了,則新建非核心線程執(zhí)行任務(wù)

必須要理清的Java線程池

其中,BlockingQueue中具體的API介紹:

offer(E e): 將給定的元素設(shè)置到隊(duì)列中,如果設(shè)置成功返回true, 否則返回false. e的值不能為空,否則拋出空指針異常。

offer(E e, long timeout, TimeUnit unit): 將給定元素在給定的時(shí)間內(nèi)設(shè)置到隊(duì)列中,如果設(shè)置成功返回true, 否則返回false.

add(E e): 將給定元素設(shè)置到隊(duì)列中,如果設(shè)置成功返回true, 否則拋出異常。如果是往限定了長(zhǎng)度的隊(duì)列中設(shè)置值,推薦使用offer()方法。

put(E e): 將元素設(shè)置到隊(duì)列中,如果隊(duì)列中沒有多余的空間,該方法會(huì)一直阻塞,直到隊(duì)列中有多余的空間。

take(): 從隊(duì)列中獲取值,如果隊(duì)列中沒有值,線程會(huì)一直阻塞,直到隊(duì)列中有值,并且該方法取得了該值。

poll(long timeout, TimeUnit unit): 在給定的時(shí)間里,從隊(duì)列中獲取值,如果沒有取到會(huì)拋出異常。

remainingCapacity():獲取隊(duì)列中剩余的空間。

remove(Object o): 從隊(duì)列中移除指定的值。

contains(Object o): 判斷隊(duì)列中是否擁有該值。

drainTo(Collection c): 將隊(duì)列中值,全部移除,并發(fā)設(shè)置到給定的集合中。

說完了BlockingQueue常用的API,在說說其常用的workQueue類型:

一般來說,workQueue有以下四種隊(duì)列類型:

SynchronousQueue:(同步隊(duì)列)這個(gè)隊(duì)列接收到任務(wù)的時(shí)候,會(huì)直接提交給線程處理,而不保留它(名字定義為 同步隊(duì)列)。但有一種情況,假設(shè)所有線程都在工作怎么辦?

這種情況下,SynchronousQueue就會(huì)新建一個(gè)線程來處理這個(gè)任務(wù)。所以為了保證不出現(xiàn)(線程數(shù)達(dá)到了maximumPoolSize而不能新建線程)的錯(cuò)誤,使用這個(gè)類型隊(duì)列的時(shí)候,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大,去規(guī)避這個(gè)使用風(fēng)險(xiǎn)。

LinkedBlockingQueue(鏈表阻塞隊(duì)列):這個(gè)隊(duì)列接收到任務(wù)的時(shí)候,如果當(dāng)前線程數(shù)小于核心線程數(shù),則新建線程(核心線程)處理任務(wù);如果當(dāng)前線程數(shù)等于核心線程數(shù),則進(jìn)入隊(duì)列等待。由于這個(gè)隊(duì)列沒有最大值限制,即所有超過核心線程數(shù)的任務(wù)都將被添加到隊(duì)列中,這也就導(dǎo)致了maximumPoolSize的設(shè)定失效,因?yàn)榭偩€程數(shù)永遠(yuǎn)不會(huì)超過corePoolSize

ArrayBlockingQueue(數(shù)組阻塞隊(duì)列):可以限定隊(duì)列的長(zhǎng)度(既然是數(shù)組,那么就限定了大?。邮盏饺蝿?wù)的時(shí)候,如果沒有達(dá)到corePoolSize的值,則新建線程(核心線程)執(zhí)行任務(wù),如果達(dá)到了,則入隊(duì)等候,如果隊(duì)列已滿,則新建線程(非核心線程)執(zhí)行任務(wù),又如果總線程數(shù)到了maximumPoolSize,并且隊(duì)列也滿了,則發(fā)生錯(cuò)誤

DelayQueue(延遲隊(duì)列):隊(duì)列內(nèi)元素必須實(shí)現(xiàn)Delayed接口,這就意味著你傳進(jìn)去的任務(wù)必須先實(shí)現(xiàn)Delayed接口。這個(gè)隊(duì)列接收到任務(wù)時(shí),首先先入隊(duì),只有達(dá)到了指定的延時(shí)時(shí)間,才會(huì)執(zhí)行任務(wù)

說完了BlockingQueue,繼續(xù)回到ThreadPoolExecutor的構(gòu)造參數(shù)上面

6:ThreadFactory threadFactory = > 創(chuàng)建線程的方式,這是一個(gè)接口,new它的時(shí)候需要實(shí)現(xiàn)他的Thread newThread(Runnable r)方法

7:RejectedExecutionHandler handler = > 這個(gè)主要是用來拋異常的

當(dāng)線程無法執(zhí)行新任務(wù)時(shí)(一般是由于線程池中的線程數(shù)量已經(jīng)達(dá)到最大數(shù)或者線程池關(guān)閉導(dǎo)致的),默認(rèn)情況下,當(dāng)線程池?zé)o法處理新線程時(shí),會(huì)拋出一個(gè)RejectedExecutionException。

構(gòu)造參數(shù)基本上就介紹完畢了。

(如果看累了就先休息下。。。內(nèi)容的確較多。。。)

花了這么大篇幅去介紹ThreadPoolExecutor這個(gè)類的構(gòu)造函數(shù),可能你會(huì)覺得好累好空虛,好吧,其實(shí)我們的付出都是為打通線程池最后的壁壘做的必要準(zhǔn)備,因?yàn)?千里之行、始于足下 ,我們始終要堅(jiān)信 倘想達(dá)到最高處,就要從低處開始 。

說完了這么多,我們知道了實(shí)例化一個(gè)線程池,只需要在構(gòu)造參數(shù)里面去添加自己設(shè)置的屬性值(設(shè)置正確即可使用,設(shè)置錯(cuò)誤即拋異常),這樣問題就來了:

一個(gè)任務(wù),它是如何進(jìn)入線程池去執(zhí)行任務(wù)?

ThreadPoolExecutor這個(gè)類,里面有一個(gè)API,在上面也隨口提到過,有一個(gè)執(zhí)行的方法,先上圖

必須要理清的Java線程池

首先我們初始化一個(gè)線程池后,即可調(diào)用 execute這個(gè)方法,里面?zhèn)魅隦unnable即可向線程池添加任務(wù)。

問題又來了,既然線程池新添加了任務(wù),那么線程池是如何處理這些批量任務(wù)?

1:如果線程數(shù)量未達(dá)到corePoolSize,則新建一個(gè)線程(核心線程)執(zhí)行任務(wù)

2:如果線程數(shù)量達(dá)到了corePools,則將任務(wù)移入隊(duì)列等待

3:如果隊(duì)列已滿,新建線程(非核心線程)執(zhí)行任務(wù)

4:如果隊(duì)列已滿,總線程數(shù)又達(dá)到了maximumPoolSize,就會(huì)由RejectedExecutionHandler拋出異常

但是,實(shí)際上,Java已經(jīng)為我們提供了四種線程池!

好吧,在Java中,Executors這個(gè)類已經(jīng)為我們提供了常用的四種線程池,分別為:

A:newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。

必須要理清的Java線程池

源碼注釋翻譯:

創(chuàng)建一個(gè)線程池,使用固定數(shù)量的線程在共享的無界隊(duì)列中操作。

在任何時(shí)候,有最多 nThreads(就是我們傳入?yún)?shù)的數(shù)量)的線程將處理任務(wù)。

如果所有線程都處于活動(dòng)狀態(tài)時(shí),提交額外的任務(wù),他們會(huì)在隊(duì)列中等待,直到有一個(gè)線程可用。

如果在執(zhí)行過程中出現(xiàn)故障,任何線程都會(huì)終止。如果需要執(zhí)行后續(xù)任務(wù),新的任務(wù)將取代它的位置。線程池中的線程會(huì)一直存在,直到它顯式為止(調(diào)用shutdown)

nThreads 就是傳入線程池的數(shù)量 ,當(dāng)nThreads <= 0 就會(huì)拋異常IllegalArgumentException

B:newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

必須要理清的Java線程池

源碼注釋翻譯:

創(chuàng)建一個(gè)線程池,根據(jù)需要?jiǎng)?chuàng)建新線程,但是將重寫之前線程池的構(gòu)造。

這個(gè)線程池通常會(huì)提高性能去執(zhí)行許多短期異步任務(wù)的程序。

如果有可用線程,當(dāng)線程池調(diào)用execute, 將重用之前的構(gòu)造函數(shù)。

如果沒有現(xiàn)有的線程可用,那么就創(chuàng)建新的線程并添加到池中。

線程沒有使用60秒的時(shí)間被終止并從線程池里移除緩存。

因此,一個(gè)閑置時(shí)間足夠長(zhǎng)的線程池不消耗任何資源。

注意,線程池有類似的屬性,但有一些不同的細(xì)節(jié)(例如,超時(shí)參數(shù))可以使用@link ThreadPoolExecutor構(gòu)造函數(shù)創(chuàng)建。

C:newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)任務(wù)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。

必須要理清的Java線程池

源碼注釋翻譯:

創(chuàng)建一個(gè)線程池,它可以安排在 a 之后運(yùn)行的命令給定延遲,或定期執(zhí)行。

corePoolSize (這個(gè)參數(shù)) 是指在池中保留的線程數(shù),即使它們是空閑的。這個(gè)函數(shù)最終會(huì)返回一個(gè)新創(chuàng)建的調(diào)度線程池

如果 corePoolSize < 0 ,則會(huì)拋出 IllegalArgumentException

Ps:這個(gè)還支持多傳入一個(gè)ThreadFactory

D:newSingleThreadExecutor 創(chuàng)建一個(gè)單線程的線程池,它只會(huì)用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。

必須要理清的Java線程池

源碼注釋翻譯:

創(chuàng)建一個(gè)線程執(zhí)行器,它使用單個(gè)運(yùn)行中的線程操作在一個(gè)無界隊(duì)列中。

請(qǐng)注意,如果這個(gè)單獨(dú)的線程終止是因?yàn)樵趫?zhí)行前異?;蛘呓K止,若需要執(zhí)行后續(xù)的任務(wù),那么就需要一個(gè)新的去替代它。

任務(wù)被保證按順序的執(zhí)行,并且在任何給定的時(shí)間內(nèi)不超過一個(gè)任務(wù)將是活動(dòng)的。

不像其他等價(jià) newFixedThreadPool(1) 這個(gè)返回的線程池對(duì)象是保證不運(yùn)行重新配置以使用額外的線程。

最終返回的是一個(gè)重新創(chuàng)建的單線程去執(zhí)行。

總結(jié):

Java為我們提供的四種線程池基本上就介紹完畢了??梢钥吹?,這四種每一個(gè)具體的線程池都是 跟 ThreadPoolExecutor 配置有關(guān)的。因此,前面花大篇幅介紹ThreadPoolExecutor的構(gòu)造參數(shù)在這里就起到了作用,整體來說,線程池的基本概念就結(jié)束了。

下面上一份偽代碼加深理解

必須要理清的Java線程池

好了,說完了這么多,我們?cè)诨貧w到Android上面,在Android里面,線程池的應(yīng)用場(chǎng)景是什么?

這個(gè)問題面試也經(jīng)常問(面試常規(guī)套路就是,先讓你回答Java的某一個(gè)知識(shí)概念,在讓你談這一知識(shí)概念在Android的應(yīng)用場(chǎng)景)常見的有異步任務(wù)棧,(AsyncTask)其內(nèi)部就是使用到了線程池,關(guān)于異步任務(wù)棧的線程池這個(gè)網(wǎng)上資料就很多了這里就不多說了,我們今天就用上面的概念簡(jiǎn)單分析討論下OkHttp這一經(jīng)典網(wǎng)絡(luò)框架的內(nèi)置線程池。

Okhttp的常用寫法如下:

必須要理清的Java線程池

配置好請(qǐng)求體,url之后,我們會(huì)使用 OkHttpClient這個(gè)對(duì)象首先去調(diào)用 newCall,也就是上圖紅色的框框,點(diǎn)進(jìn)去這個(gè)newCall

必須要理清的Java線程池

這個(gè)方法返回了一個(gè)RealCall,翻譯過來就是 (真正的請(qǐng)求),點(diǎn)進(jìn)去看下RealCall

必須要理清的Java線程池

所以,本質(zhì)上來講 OkHttpClient.newCall(request).enqueue(), 其實(shí)就是調(diào)用 RealCall 類里面的 enqueue 方法。如下圖

必須要理清的Java線程池

黃色框框里面可以看到,這個(gè)方法最終調(diào)用的是 client.dispatcher().enqueue,這個(gè)方法內(nèi)部引用了AsyncCall這個(gè)對(duì)象,那這AsyncCall又是什么?

點(diǎn)擊AsyncCall后發(fā)現(xiàn),原來,AsyncCall 是 RealCall 的一個(gè)內(nèi)部類(位于:RealCall 源碼 124行)

必須要理清的Java線程池

可能你會(huì)問, NamedRunnable 這個(gè)類又是什么?點(diǎn)進(jìn) NamedRunnable后發(fā)現(xiàn),這個(gè)類的本質(zhì)其實(shí)就是一個(gè) Runnable

必須要理清的Java線程池

好了,言歸正傳我們?cè)倩氐絚lient.dispatcher().enqueue這個(gè)方法,點(diǎn)擊 enqueue ,進(jìn)入到了Dispatcher 這個(gè)類里面的enqueue方法(Dispatcher 翻譯過來就是:調(diào)度員、分配器)

必須要理清的Java線程池

是不是驚奇的看見了executorService().execute(call),是不是感覺有點(diǎn)類似線程池的寫法(關(guān)于 ExecutorService 這個(gè)是文章前面說到的;還有,AsyncCall本質(zhì)就是一個(gè)Runnable對(duì)象,線程池里面的execte方法里需要的就是一個(gè)Runnable對(duì)象),我們繼續(xù)點(diǎn)擊 executorService()這個(gè)方法內(nèi),看看其廬山真面目

必須要理清的Java線程池

好家伙,是不是看到了熟悉的線程池。如果只是為了應(yīng)付面試讓自己有個(gè)概念,看到這里基本上就可以了。因?yàn)榻酉聛砭烷_始深挖OkHttp內(nèi)部線程池細(xì)節(jié),內(nèi)容有點(diǎn)繞,如果愿意深挖的就請(qǐng)繼續(xù)耐心看完下面的內(nèi)容。

既然,Okhttp在這里幫我們創(chuàng)建了一個(gè)線程池。那么這個(gè)線程池是怎么處理的?通過源碼得知,構(gòu)造參數(shù)里面有一個(gè)SynchronousQueue (同步),這個(gè)在上面的構(gòu)造參數(shù)里面也分析過,這個(gè)隊(duì)列接收到任務(wù)的時(shí)候,會(huì)直接提交給線程處理,而且也提到使用這個(gè)隊(duì)列的話,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大去規(guī)避使用風(fēng)險(xiǎn),在這里,OkHttp的源碼也使用到了無限大。

我們知道,okhttp發(fā)起請(qǐng)求的步驟真正的執(zhí)行是在RealCall這個(gè)類里面,里面的enqueue方法調(diào)用了

client.dispatcher().enqueue(new AsyncCall(responseCallback)) ;

必須要理清的Java線程池

因此我們?cè)诳椿厝?Dispatcher這個(gè)類里面的enqueue方法:

必須要理清的Java線程池

這三個(gè)集合簡(jiǎn)單介紹下:

private final Deque readyAsyncCalls = new ArrayDeque<>() ; // 正在準(zhǔn)備中的異步請(qǐng)求隊(duì)列

private final Deque running AsyncCalls = new ArrayDeque<>(); //運(yùn)行中的異步請(qǐng)求

private final Deque runningSyncCalls = new ArrayDeque<>(); // 同步請(qǐng)求

拓展:Deque 這個(gè)是什么?簡(jiǎn)單理解,這個(gè)Deque它是Queue的子接口,我們知道Queue是一種隊(duì)列形式,而Deque則是雙向隊(duì)列,它支持從兩個(gè)端點(diǎn)方向檢索和插入元素(也就是頭部和尾部加入元素)

我們繼續(xù)看下enqueue方法:

必須要理清的Java線程池

在這里有個(gè)判斷,如果 (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) 如果這個(gè)條件不成立的時(shí)候,要把任務(wù)放在 readyAsyncCalls 里。

問題來了,那么什么時(shí)候去取這里的任務(wù)來執(zhí)行呢?

為了解決這個(gè)問題,我們需要在回到AsyncCall這個(gè)類一探究竟,

必須要理清的Java線程池

必須要理清的Java線程池

AsyncCall - 1圖中,截圖了三種不同顏色的矩形,是不是感覺紅色和綠色矩形對(duì)應(yīng)的兩個(gè)方法很熟悉,

responseCallback.onFailure ()實(shí)際上就是失敗的回調(diào)

responseCallback.onResponse ()實(shí)際上就是成功的回調(diào),將藍(lán)色矩形的Response對(duì)象回調(diào)出去。

那么藍(lán)色矩形里面的代碼是什么意思?getResponseWithInterceptorChain()這個(gè)方法具體是做什么的?繼續(xù)點(diǎn)進(jìn)源碼看看

必須要理清的Java線程池

哦,這個(gè)方法就是遍歷外部定義的攔截器 然后添加OkHttp內(nèi)部的攔截器去發(fā)起真正的請(qǐng)求。攔截器的本質(zhì)就是攔截請(qǐng)求體,攔截響應(yīng)體,在攔截的過程中添加信息和修改信息,比如添加請(qǐng)求頭等等。由于這個(gè)方法涉及到攔截器的源碼實(shí)在是太多了,想要細(xì)看的朋友可以在網(wǎng)上自行查閱資料。所以這里就點(diǎn)到為止。

可以看到,這個(gè)方法最終返回了一個(gè)Response對(duì)象。

回到上面那副AsyncCall - 1圖,其中紅色和綠色矩形的源碼,對(duì)應(yīng)的就是下圖OkHttp常規(guī)寫法,通過接口回調(diào)將Response結(jié)果,告知調(diào)用者,藍(lán)色矩形的response的body,就是我們請(qǐng)求成功之后獲取的響應(yīng)體。

必須要理清的Java線程池

我們看到AsyncCall - 2圖中, finally 里面有個(gè)結(jié)束的處理,也就是 client.dispatcher().finished(this) 這個(gè)方法,嘗試點(diǎn)進(jìn)去看

必須要理清的Java線程池

進(jìn)入到finished方法之后,發(fā)現(xiàn)回到了Dispatch這個(gè)類里面。這個(gè)finished方法 里面貌似能看的,就 promoteCalls 方法和runningCallsCount方法,我們首先看看runningCallsCount方法

必須要理清的Java線程池

發(fā)現(xiàn)這個(gè)方法其實(shí)返回的就是長(zhǎng)度(返回值就是int嘛),這不是我們想要的結(jié)果。所以繼續(xù)回到上一步,點(diǎn)擊promoteCalls 方法點(diǎn)進(jìn)去看看

必須要理清的Java線程池

哦,是不是終于看到了 executorService().execute(call) ,將任務(wù)添加到線程池里面的具體代碼。

這個(gè)方法的主要功能是,首先遍歷 readyAsyncCalls,把任務(wù)取出來;把取出來的任務(wù)加入 到runningAsyncCalls;最后, 把任務(wù)放入線程池。原來把任務(wù)添加到線程池是在Dispatcher這里進(jìn)行的。(這個(gè)類內(nèi)部不僅創(chuàng)建了線程池,也將任務(wù)添加了進(jìn)來)

總體來說,當(dāng)請(qǐng)求任務(wù)數(shù)大于 maxRequests 并且相同 host 最大請(qǐng)求數(shù)大于 maxRequestsPerHost,就會(huì)把請(qǐng)求任務(wù)放在 readyAsyncCalls 隊(duì)列里;當(dāng)線程池里執(zhí)行任務(wù)的 runnable 執(zhí)行完任務(wù)在最后會(huì)檢查 readyAsyncCalls 里有沒有任務(wù),如果有任務(wù)并且是同一個(gè) host 就放入到線程池中執(zhí)行。因此通過這個(gè)方法不斷地從 readyAsyncCalls 隊(duì)列里取出任務(wù),對(duì)線程池里的線程進(jìn)行復(fù)用。

關(guān)于Okhttp內(nèi)置線程池和部分源碼的分析就寫到這里了,希望對(duì)開發(fā)者有一點(diǎn)點(diǎn)小幫助吧。如果面試官在問到線程池在Android的實(shí)際應(yīng)用的時(shí)候,我們除了說異步任務(wù)棧(面試官可能也準(zhǔn)備好了異步任務(wù)棧讓你入坑跟你討論其內(nèi)部細(xì)節(jié)),可以直接跟他談OkHttp框架內(nèi)部如何創(chuàng)建線程池、如何將任務(wù)添加到線程池等具體的內(nèi)部實(shí)現(xiàn)流程。

如果這篇文章對(duì)你有幫助,希望開發(fā)者朋友可以留下寶貴的star,謝謝。

參考資料:https://www.jianshu.com/p/210eab345423參考資料:https://www.cnblogs.com/miller-zou/p/6978533.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多