自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

漫畫(huà):聊聊線程池中,線程的增長(zhǎng)/回收策略

開(kāi)發(fā)
我們今天就來(lái)借這個(gè)問(wèn)題,聊聊線程池中維護(hù)的線程,它增長(zhǎng)和回收的策略是什么樣的?

 一、序

 

 

public static ExecutorService newThreadPool() { return new ThreadPoolExecutor( 30, 60, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());}

 

 

 

 

我們今天就來(lái)借這個(gè)問(wèn)題,聊聊線程池中維護(hù)的線程,它增長(zhǎng)和回收的策略是什么樣的?

二、線程池的策略

2.1 線程池的各項(xiàng)參數(shù)

當(dāng)我們聊到線程池中線程的增長(zhǎng)策略的時(shí)候,最抓眼球的就是它的核心線程數(shù)(corePoolSize)和最大線程數(shù)(maximumPoolSize),但是僅看這兩個(gè)參數(shù)是不夠全面的,線程數(shù)量的增長(zhǎng),還與任務(wù)等待隊(duì)列有關(guān)系。

我們先來(lái)看看 ThreadPoolExecutor 最全參數(shù)的構(gòu)造方法:

  1. public ThreadPoolExecutor( 
  2.   int corePoolSize, 
  3.   int maximumPoolSize, 
  4.   long keepAliveTime, 
  5.   TimeUnit unit, 
  6.   BlockingQueue<Runnable> workQueue, 
  7.   ThreadFactory threadFactory, 
  8.   RejectedExecutionHandler handler) { 
  9.   // ... 

簡(jiǎn)單解釋一下各個(gè)參數(shù)是什么意思:

  • corePoolSize:核心線程數(shù);
  • maximumPoolSize:線程池的最大線程數(shù);
  • keepAliveTime:核心線程數(shù)之外的線程,最大空閑存活的時(shí)長(zhǎng);
  • unit:keepAliveTime 的時(shí)間單位;
  • workQueue:線程池的任務(wù)等待隊(duì)列;
  • threadFractory:線程工廠,用來(lái)為線程池創(chuàng)建線程;
  • handler:拒絕策略,當(dāng)線程池?zé)o法處理任務(wù)時(shí)的拒絕方式;

這其中很多參數(shù)的配置,都是相互影響的。例如任務(wù)等待隊(duì)列 workQueue 配置不當(dāng),可能導(dǎo)致線程池中的線程,永遠(yuǎn)無(wú)法增長(zhǎng)到核心線程數(shù)(maximumPoolSize)配置的線程數(shù)。

2.2 線程池中線程的增長(zhǎng)策略

看到這里你應(yīng)該就清楚了,線程池線程的增長(zhǎng)策略,和 3 個(gè)參數(shù)有關(guān)系:

  • corePoolSize:核心線程數(shù)
  • maximumPoolSize:最大線程數(shù);
  • workQueue:等待任務(wù)隊(duì)列;

它們之前的關(guān)系是這樣的:

 

 

 

 

接下來(lái)我們看看理想情況下,線程池中線程的增長(zhǎng)策略。

默認(rèn)情況下,初始時(shí)線程池是空的,當(dāng)有新任務(wù)來(lái)了時(shí),線程池開(kāi)始通過(guò)線程工廠(threadFractory)創(chuàng)建線程來(lái)處理任務(wù)。

新的任務(wù)會(huì)不斷的觸發(fā)線程池中線程的創(chuàng)建,直到線程數(shù)量達(dá)到核心線程數(shù)(corePoolSize),接下來(lái)會(huì)停止線程的創(chuàng)建,而是將這個(gè)新任務(wù)放入任務(wù)等待隊(duì)列(workQueue)。

新任務(wù)不斷進(jìn)入任務(wù)等待隊(duì)列,當(dāng)該隊(duì)列滿了時(shí),開(kāi)始重新創(chuàng)建線程處理任務(wù),直到線程池中線程的數(shù)量,到達(dá) maximumPoolSize 配置的數(shù)量。

到這一步時(shí),線程池的線程數(shù)達(dá)到最大值,并且沒(méi)有空閑的線程,任務(wù)隊(duì)列也存滿了任務(wù),這時(shí)如果還有新的任務(wù)進(jìn)來(lái),就會(huì)觸發(fā)線程池的拒絕策略(handler),如果沒(méi)有配置拒絕策略就會(huì)拋出 RejectedExecutionException 異常。

到這里線程的增長(zhǎng)策略就說(shuō)清楚了,我們可以通過(guò)下圖來(lái)了解完整的流程。

 

 

 

 

其中比較關(guān)鍵的就是任務(wù)的等待隊(duì)列,無(wú)論等待隊(duì)列的實(shí)現(xiàn)結(jié)構(gòu)是什么樣的,只有在它滿的時(shí)候,線程池中的線程才會(huì)向最大線程數(shù)增長(zhǎng)。但是一個(gè)能夠滿的隊(duì)列,它的前提是必須是一個(gè)有界隊(duì)列。

這就是文章開(kāi)頭舉的例子暗藏的坑,我們回顧一下前面構(gòu)造的線程池。

  1. public static ExecutorService newThreadPool() { 
  2.   return new ThreadPoolExecutor( 
  3.     30, 60, 
  4.     60L, TimeUnit.MILLISECONDS, 
  5.     new LinkedBlockingQueue<Runnable>()); 

可以看到,這里雖然最大線程數(shù)是大于核心線程數(shù)的,但是它的等待隊(duì)列配置的是一個(gè) LinkedBlockingQueue,從名字上可以看出這是一個(gè)基于鏈表實(shí)現(xiàn)的阻塞隊(duì)列,而用它的默認(rèn)構(gòu)造方法構(gòu)造時(shí),其容量設(shè)定為 Integer.MAX_VALUE,可以簡(jiǎn)單理解它是一個(gè)無(wú)界隊(duì)列。

  1. public LinkedBlockingQueue() { 
  2.   this(Integer.MAX_VALUE); 
  3.  
  4. public LinkedBlockingQueue(int capacity) { 
  5.   if (capacity <= 0) throw new IllegalArgumentException(); 
  6.   this.capacity = capacity; 
  7.   last = head = new Node<E>(null); 

這也就是為什么說(shuō),這樣構(gòu)造的線程池,核心線程數(shù)的配置參數(shù),永遠(yuǎn)都用不到,因?yàn)樗牡却?duì)列永遠(yuǎn)沒(méi)有滿的時(shí)候。

2.3 線程池中線程的收縮策略

線程池中執(zhí)行的任務(wù),總有執(zhí)行結(jié)束的時(shí)候。那么線程池當(dāng)線程池中存在大量空閑線程時(shí),也會(huì)有一定的收縮策略,來(lái)回收線程池中多余的線程。

線程池中線程的收縮策略,和以下幾個(gè)參數(shù)相關(guān):

  • corePoolSize:核心線程數(shù);
  • maximumPoolSize:線程池的最大線程數(shù);
  • keepAliveTime:核心線程數(shù)之外的線程,空閑存活的時(shí)長(zhǎng);
  • unit:keepAliveTime 的時(shí)間單位;

corePoolSize 和 maximumPoolSize 我們比較熟悉了,另外能夠控制它的就是 keepAliveTime 空閑存活時(shí)長(zhǎng),以及這個(gè)時(shí)長(zhǎng)的單位。

當(dāng)線程池中的線程數(shù),超過(guò)核心線程數(shù)時(shí)。此時(shí)如果任務(wù)量下降,肯定會(huì)出現(xiàn)有一些線程處于無(wú)任務(wù)執(zhí)行的空閑狀態(tài)。那么如果這個(gè)線程的空閑時(shí)間超過(guò)了 keepAliveTime&unit 配置的時(shí)長(zhǎng)后,就會(huì)被回收。

需要注意的是,對(duì)于線程池來(lái)說(shuō),它只負(fù)責(zé)管理線程,對(duì)于創(chuàng)建的線程是不區(qū)分所謂的「核心線程」和「非核心線程」的,它只對(duì)線程池中的線程總數(shù)進(jìn)行管理,當(dāng)回收的線程數(shù)達(dá)到 corePoolSize 時(shí),回收的過(guò)程就會(huì)停止。

對(duì)于線程池的核心線程數(shù)中的線程,也有回收的辦法,可以通過(guò) allowCoreThreadTimeOut(true) 方法設(shè)置,在核心線程空閑的時(shí)候,一旦超過(guò) keepAliveTime&unit 配置的時(shí)間,也將其回收掉。

 

  1. public void allowCoreThreadTimeOut(boolean value) { 
  2.   if (value && keepAliveTime <= 0) 
  3.     throw new IllegalArgumentException("Core threads must have nonzero keep alive times"); 
  4.   if (value != allowCoreThreadTimeOut) { 
  5.     allowCoreThreadTimeOut = value; 
  6.     if (value) 
  7.       interruptIdleWorkers(); 
  8.   } 

allowCoreThreadTimeOut() 能被設(shè)置的前提是 keepAliveTime 不能為 0。

2.3 查缺補(bǔ)漏

1. 等待隊(duì)列還會(huì)影響拒絕策略

等待隊(duì)列如果配置成了無(wú)界隊(duì)列,不光影響線程數(shù)量從核心線程數(shù)向最大線程數(shù)的增長(zhǎng),還會(huì)導(dǎo)致配置的拒絕策略永遠(yuǎn)得不到執(zhí)行。

因?yàn)橹挥性诰€程池中的工作線程數(shù)量已經(jīng)達(dá)到核心線程數(shù),并且此時(shí)等待隊(duì)列也滿了的情況下,拒絕策略才能生效。

2. 核心線程數(shù)可以被「預(yù)熱」

前面提到默認(rèn)的情況下,線程池中的線程是根據(jù)任務(wù)來(lái)增長(zhǎng)的。但如果有需要,我們也可以提前準(zhǔn)備好線程池的核心線程,來(lái)應(yīng)對(duì)突然的高并發(fā)任務(wù),例如在搶購(gòu)系統(tǒng)中就經(jīng)常有這樣的需要。

此時(shí)就可以利用 prestartCoreThread() 或者 prestartAllCoreThreads() 來(lái)提前創(chuàng)建核心線程,這種方式被我們稱(chēng)為「預(yù)熱」。

3. 對(duì)于需要無(wú)界隊(duì)列的場(chǎng)景,怎么辦?

需求是多變的,我們肯定會(huì)碰到需要使用無(wú)界隊(duì)列的場(chǎng)景,那么這種場(chǎng)景下配置的 maximumPoolSize 就是無(wú)效的。

此時(shí)就可以參考 Executors 中 newFixedThreadPool() 創(chuàng)建線程池的過(guò)程,將 corePoolSize 和 maximumPoolSize 保持一致即可。

  1. public static ExecutorService newFixedThreadPool(int nThreads) { 
  2.   return new ThreadPoolExecutor( 
  3.     nThreads, nThreads, 
  4.     0L, TimeUnit.MILLISECONDS, 
  5.     new LinkedBlockingQueue<Runnable>()); 

此時(shí)核心線程數(shù)就是最大線程數(shù),只有增長(zhǎng)到這個(gè)數(shù)量才會(huì)將任務(wù)放入等待隊(duì)列,來(lái)保證我們配置的線程數(shù)量都得到了使用。

4. 線程池是公平的嗎?

所謂的公平,就是先到的任務(wù)會(huì)被先執(zhí)行。這在線程池中,顯然是不公平的。

不提線程池中線程執(zhí)行任務(wù)是通過(guò)系統(tǒng)去調(diào)度的,這一點(diǎn)就決定了任務(wù)的執(zhí)行順序是無(wú)法保證的,這就是是非公平的。另外只從線程池本身的角度來(lái)看,我們只看提交的任務(wù)順序來(lái)看,它也是非公平的。

首先前面到的任務(wù),如果線程池的核心線程已經(jīng)分配出去了,此時(shí)這個(gè)任務(wù)就會(huì)進(jìn)入任務(wù)隊(duì)列,那么如果任務(wù)隊(duì)列滿了之后,新到的任務(wù)會(huì)直接由線程池新創(chuàng)建線程去處理,直到線程數(shù)達(dá)到最大線程數(shù)。

那么此時(shí),任務(wù)隊(duì)列中的任務(wù),雖然先添加進(jìn)線程池等待處理,但是這些任務(wù)的處理時(shí)機(jī),是晚于后續(xù)新創(chuàng)建線程去處理的任務(wù)的,所以說(shuō)僅從任務(wù)的角度,依然是非公平的。

三、小結(jié)時(shí)刻

本文我們聊到了線程池中,對(duì)于線程數(shù)量的增長(zhǎng)和收縮策略。

在這里我們簡(jiǎn)單總結(jié)一下:

1. 增長(zhǎng)策略。默認(rèn)情況下,線程池是根據(jù)任務(wù)先創(chuàng)建足夠核心線程數(shù)的線程去執(zhí)行任務(wù),當(dāng)核心線程滿了時(shí)將任務(wù)放入等待隊(duì)列。待隊(duì)列滿了的時(shí)候,繼續(xù)創(chuàng)建新線程執(zhí)行任務(wù)直到到達(dá)最大線程數(shù)停止。再有新任務(wù)的話,那就只能執(zhí)行拒絕策略或是拋出異常。

2. 收縮策略。當(dāng)線程池線程數(shù)量大于核心線程數(shù) && 當(dāng)前有空閑線程 && 空閑線程的空閑時(shí)間大于 keepAliveTime 時(shí),會(huì)對(duì)該空閑線程進(jìn)行回收,直到線程數(shù)量等于核心線程數(shù)為止。

總之要謹(jǐn)記,慎用無(wú)界隊(duì)列。

責(zé)任編輯:武曉燕 來(lái)源: 承香墨影
相關(guān)推薦

2024-10-11 16:57:18

2024-04-02 09:53:08

線程池線程堆棧

2024-04-08 10:09:37

TTLJava框架

2020-12-28 08:03:26

多線程進(jìn)程瀏覽器

2024-08-29 08:54:35

2024-06-13 09:30:33

Java線程池線程

2023-02-02 08:56:25

線程池線程submit

2025-02-05 14:28:19

2023-04-02 17:53:10

多線程編程自測(cè)

2024-03-12 13:11:20

powerjob單機(jī)線程

2024-12-02 10:04:04

2021-03-11 00:07:30

線程Thread程序

2022-02-07 11:55:00

linux進(jìn)程線程

2024-10-21 16:59:37

C#編程多線程

2021-02-01 08:28:24

Linux線程池Linux系統(tǒng)

2022-08-29 09:06:43

hippo4j動(dòng)態(tài)線程池

2021-11-29 10:55:11

線程池Java面試

2022-06-07 23:28:05

線程安全后端

2023-10-26 08:25:35

Java線程周期

2024-06-04 07:52:04

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)