溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java中為什么要用線程池

發(fā)布時(shí)間:2021-12-20 10:37:23 來源:億速云 閱讀:215 作者:小新 欄目:大數(shù)據(jù)

這篇文章給大家分享的是有關(guān)java中為什么要用線程池的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。

為什么要用線程池

使用線程池管理線程有如下優(yōu)點(diǎn):

  1. 降低資源消耗:通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。

  2. 提高響應(yīng)速度:當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。

  3. 提高線程的可管理性:線程是稀缺資源,如果無限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。

線程池介紹

ThreadPoolExecutor

Java 為我們提供了 ThreadPoolExecutor 來創(chuàng)建一個(gè)線程池,其完整構(gòu)造函數(shù)如下所示:

java中為什么要用線程池

  • int corePoolSize(核心線程數(shù)):線程池新建線程的時(shí)候,如果當(dāng)前線程總數(shù)小于corePoolSize,則新建的是核心線程,如果超過corePoolSize,則新建的是非核心線程;核心線程默認(rèn)情況下會(huì)一直存活在線程池中,即使這個(gè)核心線程啥也不干(閑置狀態(tài));如果設(shè)置了 allowCoreThreadTimeOut 為 true,那么核心線程如果不干活(閑置狀態(tài))的話,超過一定時(shí)間(時(shí)長(zhǎng)下面參數(shù)決定),就會(huì)被銷毀掉。

  • int maximumPoolSize(線程池能容納的最大線程數(shù)量):線程總數(shù) = 核心線程數(shù) + 非核心線程數(shù)。

  • long keepAliveTime(非核心線程空閑存活時(shí)長(zhǎng)):非核心線程空閑時(shí)長(zhǎng)超過該時(shí)長(zhǎng)將會(huì)被回收,主要應(yīng)用在緩存線程池中,當(dāng)設(shè)置了 allowCoreThreadTimeOut 為 true 時(shí),對(duì)核心線程同樣起作用。

  • TimeUnit unit(keepAliveTime 的單位):它是一個(gè)枚舉類型,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)。

  • BlockingQueue workQueue(任務(wù)隊(duì)列):當(dāng)所有的核心線程都在干活時(shí),新添加的任務(wù)會(huì)被添加到這個(gè)隊(duì)列中等待處理,如果隊(duì)列滿了,則新建非核心線程執(zhí)行任務(wù),常用的 workQueue 類型:

    1. SynchronousQueue:這個(gè)隊(duì)列接收到任務(wù)的時(shí)候,會(huì)直接提交給線程處理,而不保留它,如果所有線程都在工作怎么辦?那就新建一個(gè)線程來處理這個(gè)任務(wù)!所以為了保證不出現(xiàn) 線程數(shù)達(dá)到了 maximumPoolSize 而不能新建線程 的錯(cuò)誤,使用這個(gè)類型隊(duì)列的時(shí)候,maximumPoolSize 一般指定成 Integer.MAX_VALUE,即無限大。

    2. LinkedBlockingQueue:這個(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。

    3. ArrayBlockingQueue:可以限定隊(duì)列的長(zhǎng)度,接收到任務(wù)的時(shí)候,如果沒有達(dá)到 corePoolSize 的值,則新建線程(核心線程)執(zhí)行任務(wù),如果達(dá)到了,則入隊(duì)等候,如果隊(duì)列已滿,則新建線程(非核心線程)執(zhí)行任務(wù),又如果總線程數(shù)到了 maximumPoolSize,并且隊(duì)列也滿了,則發(fā)生錯(cuò)誤。

    4. DelayQueue:隊(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ù)。

  • ThreadFactory threadFactory(線程工廠):用來創(chuàng)建線程池中的線程,通常用默認(rèn)的即可。

  • RejectedExecutionHandler handler(拒絕策略):在線程池已經(jīng)關(guān)閉的情況下和任務(wù)太多導(dǎo)致最大線程數(shù)和任務(wù)隊(duì)列已經(jīng)飽和,無法再接收新的任務(wù),在上面兩種情況下,只要滿足其中一種時(shí),在使用 execute() 來提交新的任務(wù)時(shí)將會(huì)拒絕,線程池提供了以下 4 種策略:

    1. AbortPolicy:默認(rèn)策略,在拒絕任務(wù)時(shí),會(huì)拋出RejectedExecutionException。

    2. CallerRunsPolicy:只要線程池未關(guān)閉,該策略直接在調(diào)用者線程中,運(yùn)行當(dāng)前的被丟棄的任務(wù)。

    3. DiscardOldestPolicy:該策略將丟棄最老的一個(gè)請(qǐng)求,也就是即將被執(zhí)行的任務(wù),并嘗試再次提交當(dāng)前任務(wù)。

    4. DiscardPolicy:該策略默默的丟棄無法處理的任務(wù),不予任何處理。

線程池執(zhí)行策略

當(dāng)一個(gè)任務(wù)要被添加進(jìn)線程池時(shí),有以下四種執(zhí)行策略:

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

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

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

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

其流程圖如下所示:

java中為什么要用線程池

常見的四類線程池

常見的四類線程池分別有 FixedThreadPool、SingleThreadExecutor、ScheduledThreadPool 和 CachedThreadPool,它們其實(shí)都是通過 ThreadPoolExecutor 創(chuàng)建的

合理地配置線程池

需要針對(duì)具體情況而具體處理,不同的任務(wù)類別應(yīng)采用不同規(guī)模的線程池,任務(wù)類別可劃分為 CPU 密集型任務(wù)、IO 密集型任務(wù)和混合型任務(wù)。

  • CPU 密集型任務(wù):線程池中線程個(gè)數(shù)應(yīng)盡量少,推薦配置為 (CPU 核心數(shù) + 1);

  • IO 密集型任務(wù):由于 IO 操作速度遠(yuǎn)低于 CPU 速度,那么在運(yùn)行這類任務(wù)時(shí),CPU 絕大多數(shù)時(shí)間處于空閑狀態(tài),那么線程池可以配置盡量多些的線程,以提高 CPU 利用率,推薦配置為 (2 * CPU 核心數(shù) + 1);

  • 混合型任務(wù):可以拆分為 CPU 密集型任務(wù)和 IO 密集型任務(wù),當(dāng)這兩類任務(wù)執(zhí)行時(shí)間相差無幾時(shí),通過拆分再執(zhí)行的吞吐率高于串行執(zhí)行的吞吐率,但若這兩類任務(wù)執(zhí)行時(shí)間有數(shù)據(jù)級(jí)的差距,那么沒有拆分的意義。

線程池工具類封裝及使用

為了提升開發(fā)效率及更好地使用和管理線程池,我已經(jīng)為你們封裝好了線程工具類----ThreadUtils,依賴 AndroidUtilCode 1.16.1 版本即可使用,其 API 如下所示:

java中為什么要用線程池

如果你使用 RxJava 很 6,而且項(xiàng)目中已經(jīng)使用了 RxJava,那么你可以繼續(xù)使用 RxJava 來做線程切換的操作;如果你并不會(huì) RxJava 或者是在開發(fā) SDK,那么這個(gè)工具類再適合你不過了,它可以為你統(tǒng)一管理線程池的使用,不至于讓你的項(xiàng)目中出現(xiàn)過多的線程池。

ThreadUtils 使用極為方便,看 API 即可明白相關(guān)意思,F(xiàn)ixedPool、SinglePool、CachedPool 分別對(duì)應(yīng)了上面介紹的 FixedThreadPool、SingleThreadExecutor、CachedThreadPool 這三種,IoPool 是創(chuàng)建 (CPU_COUNT * 2 + 1) 個(gè)核心線程數(shù),CpuPool 是建立 (CPU_COUNT + 1) 個(gè)核心線程數(shù);而所有的 execute 都是線程池外圍裹了一層 ScheduledThreadPool,這里和 RxJava 線程池的實(shí)現(xiàn)有所相似,可以更方便地提供延時(shí)任務(wù)和固定頻率執(zhí)行的任務(wù),當(dāng)然也可以更方便地取消任務(wù)的執(zhí)行,下面讓我們來簡(jiǎn)單地來介紹其使用,以從 assets 中拷貝 APK 到 SD 卡為例,其代碼如下所示:

java中為什么要用線程池

看起來還不是很優(yōu)雅是吧,你可以把相關(guān)的 Task 都抽出來放到合適的包下,這樣每個(gè) Task 的指責(zé)一看便知,如上例子可以改裝成如下所示:

java中為什么要用線程池

是不是瞬間清爽了很多,如果執(zhí)行成功的回調(diào)中涉及了 View 相關(guān)的操作,那么你需要在 destroy 中取消 task 的執(zhí)行哦,否則會(huì)內(nèi)存泄漏哦,繼續(xù)你上面的例子為例,代碼如下所示:

java中為什么要用線程池

以上是以 SimpleTask 為例,Task 的話會(huì)多兩個(gè)回調(diào),onCancel() 和 onFail(Throwable t),它們和 onSuccess(T result) 都是互斥的,最終回調(diào)只會(huì)走它們其中之一,并且在 Android 端是發(fā)送到主線程中執(zhí)行,如果是 Java 端的話那就還是會(huì)在相應(yīng)的線程池中執(zhí)行,這點(diǎn)也方便了我做單元測(cè)試。

感謝各位的閱讀!關(guān)于“java中為什么要用線程池”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI