本文目錄一覽:
- 1、【Java基礎】線程池的原理是什麼?
- 2、ThreadPoolExcutor用法詳解
- 3、什麼是java線程池
- 4、線程池創建的7個參數
- 5、java常用的幾種線程池實例講解
- 6、java線程池的理解
【Java基礎】線程池的原理是什麼?
什麼是線程池?
總歸為:池化技術 —》數據庫連接池 緩存架構 緩存池 線程池 內存池,連接池,這種思想演變成緩存架構技術— JDK設計思想有千絲萬縷的聯繫
首先我們從最核心的ThreadPoolExecutor類中的方法講起,然後再講述它的實現原理,接着給出了它的使用示例,最後討論了一下如何合理配置線程池的大小。
Java 中的 ThreadPoolExecutor 類
java.uitl.concurrent.ThreadPoolExecutor 類是線程池中最核心的一個類,因此如果要透徹地了解Java 中的線程池,必須先了解這個類。下面我們來看一下 ThreadPoolExecutor 類的具體實現源碼。
在 ThreadPoolExecutor 類中提供了四個構造方法:
從上面的代碼可以得知,ThreadPoolExecutor 繼承了 AbstractExecutorService 類,並提供了四個構造器,事實上,通過觀察每個構造器的源碼具體實現,發現前面三個構造器都是調用的第四個構造器進行的初始化工作。
下面解釋下一下構造器中各個參數的含義:
corePoolSize:核心池的大小,這個參數跟後面講述的線程池的實現原理有非常大的關係。在創建了線程池後,默認情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了prestartAllCoreThreads() 或者 prestartCoreThread()方法,從這 2 個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建 corePoolSize 個線程或者一個線程。默認情況下,在創建了線程池後,線程池中的線程數為0,當有任務來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到 corePoolSize 後,就會把到達的任務放到緩存隊列當中;
maximumPoolSize:線程池最大線程數,這個參數也是一個非常重要的參數,它表示在線程池中最多能創建多少個線程;
keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大於 corePoolSize 時,keepAliveTime 才會起作用,直到線程池中的線程數不大於 corePoolSize,即當線程池中的線程數大於 corePoolSize 時,如果一個線程空閑的時間達到 keepAliveTime,則會終止,直到線程池中的線程數不超過 corePoolSize。但是如果調用了 allowCoreThreadTimeOut(boolean) 方法,在線程池中的線程數不大於 corePoolSize 時,keepAliveTime 參數也會起作用,直到線程池中的線程數為0;
unit:參數 keepAliveTime 的時間單位,有 7 種取值,在 TimeUnit 類中有 7 種靜態屬性:
workQueue:一個阻塞隊列,用來存儲等待執行的任務,這個參數的選擇也很重要,會對線程池的運行過程產生重大影響,一般來說,這裡的阻塞隊列有以下幾種選擇:
ArrayBlockingQueue 和 PriorityBlockingQueue 使用較少,一般使用 LinkedBlockingQueue 和 Synchronous。線程池的排隊策略與 BlockingQueue 有關。
threadFactory:線程工廠,主要用來創建線程;
handler:表示當拒絕處理任務時的策略,有以下四種取值:
具體參數的配置與線程池的關係將在下一節講述。
從上面給出的 ThreadPoolExecutor 類的代碼可以知道,ThreadPoolExecutor 繼承了AbstractExecutorService,我們來看一下 AbstractExecutorService 的實現:
AbstractExecutorService 是一個抽象類,它實現了 ExecutorService 接口。
我們接着看 ExecutorService 接口的實現:
而 ExecutorService 又是繼承了 Executor 接口,我們看一下 Executor 接口的實現:
ThreadPoolExcutor用法詳解
java線程池用法舉例:
1、ThreadPoolExecutor executor =new ThreadPoolExecutor(2,10,30, TimeUnit.SECONDS,new ArrayBlockingQueue(100));
2、ThreadPoolExecutor executor2 =new ThreadPoolExecutor(2,10,30, TimeUnit.SECONDS,new LinkedBlockingDeque());
知道了各個參數的作用後,我們開始構造符合我們期待的線程池。首先看JDK給我們預定義的幾種線程池:
一、預定義線程池
FixedThreadPool
publicstaticExecutorServicenewFixedThreadPool(intnThreads){returnnewThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,newLinkedBlockingQueue()); }
corePoolSize與maximumPoolSize相等,即其線程全為核心線程,是一個固定大小的線程池,是其優勢;
keepAliveTime = 0 該參數默認對核心線程無效,而FixedThreadPool全部為核心線程;
workQueue 為LinkedBlockingQueue(無界阻塞隊列),隊列最大值為Integer.MAX_VALUE。如果任務提交速度持續大余任務處理速度,會造成隊列大量阻塞。因為隊列很大,很有可能在拒絕策略前,內存溢出。是其劣勢;
FixedThreadPool的任務執行是無序的;
適用場景:可用於Web服務瞬時削峰,但需注意長時間持續高峰情況造成的隊列阻塞。
CachedThreadPool
publicstaticExecutorServicenewCachedThreadPool(){returnnewThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,newSynchronousQueue()); }
corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即線程數量幾乎無限制;
keepAliveTime = 60s,線程空閑60s後自動結束。
workQueue 為 SynchronousQueue 同步隊列,這個隊列類似於一個接力棒,入隊出隊必須同時傳遞,因為CachedThreadPool線程創建無限制,不會有隊列等待,所以使用SynchronousQueue;
適用場景:快速處理大量耗時較短的任務,如Netty的NIO接受請求時,可使用CachedThreadPool。
SingleThreadExecutor
publicstaticExecutorServicenewSingleThreadExecutor(){returnnewFinalizableDelegatedExecutorService (newThreadPoolExecutor(1,1,0L, TimeUnit.MILLISECONDS,newLinkedBlockingQueue())); }
咋一瞅,不就是newFixedThreadPool(1)嗎?定眼一看,這裡多了一層FinalizableDelegatedExecutorService包裝,這一層有什麼用呢,寫個dome來解釋一下:
publicstaticvoidmain(String[] args){ ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1); ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService; System.out.println(threadPoolExecutor.getMaximumPoolSize()); threadPoolExecutor.setCorePoolSize(8); ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();// 運行時異常 java.lang.ClassCastException// ThreadPoolExecutor threadPoolExecutor2 = (ThreadPoolExecutor) singleExecutorService;}
對比可以看出,FixedThreadPool可以向下轉型為ThreadPoolExecutor,並對其線程池進行配置,而SingleThreadExecutor被包裝後,無法成功向下轉型。 因此,SingleThreadExecutor被定以後,無法修改,做到了真正的Single。
ScheduledThreadPool
publicstaticScheduledExecutorServicenewScheduledThreadPool(intcorePoolSize){returnnewScheduledThreadPoolExecutor(corePoolSize); }
newScheduledThreadPool調用的是ScheduledThreadPoolExecutor的構造方法,而ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor,構造是還是調用了其父類的構造方法。
publicScheduledThreadPoolExecutor(intcorePoolSize){super(corePoolSize, Integer.MAX_VALUE,0, NANOSECONDS,newDelayedWorkQueue()); }
二、自定義線程池
什麼是java線程池
找的資料,你看一下吧:
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。
假設一個服務器完成一項任務所需時間為:T1
創建線程時間,T2
在線程中執行任務的時間,T3
銷毀線程時間。
如果:T1
+
T3
遠大於
T2,則可以採用線程池,以提高服務器性能。
一個線程池包括以下四個基本組成部分:
1、線程池管理器(ThreadPool):用於創建並管理線程池,包括
創建線程池,銷毀線程池,添加新任務;
2、工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;
3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等;
4、任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩衝機制。
線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的。它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。
線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:
假設一個服務器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小於50000。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。
線程池創建的7個參數
java多線程開發時,常常用到線程池技術,這篇文章是對創建java線程池時的七個參數的詳細解釋。從源碼中可以看出,線程池的構造函數有7個參數,分別是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。下面會對這7個參數一一解釋。
線程池中會維護一個最小的線程數量,即使這些線程處理空閑狀態,他們也不會 被銷毀,除非設置了allowCoreThreadTimeOut。這裡的最小線程數量即是corePoolSize。
一個任務被提交到線程池後,首先會緩存到工作隊列(後面會介紹)中,如果工作隊列滿了,則會創建一個新線程,然後從工作隊列中的取出一個任務交由新線程來處理,而將剛提交的任務放入工作隊列。線程池不會無限制的去創建新線程,它會有一個最大線程數量的限制,這個數量即由maximunPoolSize來指定。
一個線程如果處於空閑狀態,並且當前的線程數量大於corePoolSize,那麼在指定時間後,這個空閑線程會被銷毀,這裡的指定時間由keepAliveTime來設定。
keepAliveTime的計量單位
新任務被提交後,會先進入到此工作隊列中,任務調度時再從隊列中取出任務。jdk中提供了四種工作隊列:
基於數組的有界阻塞隊列,按FIFO排序。新任務進來後,會放到該隊列的隊尾,有界的數組可以防止資源耗盡問題。當線程池中線程數量達到corePoolSize後,再有新任務進來,則會將任務放入該隊列的隊尾,等待被調度。如果隊列已經是滿的,則創建一個新線程,如果線程數量已經達到maxPoolSize,則會執行拒絕策略。
基於鏈表的無界阻塞隊列(其實最大容量為Interger.MAX),按照FIFO排序。由於該隊列的近似無界性,當線程池中線程數量達到corePoolSize後,再有新任務進來,會一直存入該隊列,而不會去創建新線程直到maxPoolSize,因此使用該工作隊列時,參數maxPoolSize其實是不起作用的。
一個不緩存任務的阻塞隊列,生產者放入一個任務必須等到消費者取出這個任務。也就是說新任務進來時,不會緩存,而是直接被調度執行該任務,如果沒有可用線程,則創建新線程,如果線程數量達到maxPoolSize,則執行拒絕策略。
具有優先級的無界阻塞隊列,優先級通過參數Comparator實現。
創建一個新線程時使用的工廠,可以用來設定線程名、是否為daemon線程等等
當工作隊列中的任務已到達最大限制,並且線程池中的線程數量也達到最大限制,這時如果有新任務提交進來,該如何處理呢。這裡的拒絕策略,就是解決這個問題的,jdk中提供了4中拒絕策略:
該策略下,在 調用者線程 中直接執行該被拒絕任務的run方法,除非線程池已經shutdown,則直接拋棄任務。
該策略下,直接丟棄任務,並拋出RejectedExecutionException異常。
該策略下,直接丟棄任務,什麼都不做。
該策略下,拋棄進入隊列最早的那個任務,然後嘗試把這次拒絕的任務放入隊列
到此,構造線程池時的七個參數,就全部介紹完畢了。
java常用的幾種線程池實例講解
下面給你介紹4種線程池:
1、newCachedThreadPool:
底層:返回ThreadPoolExecutor實例,corePoolSize為0;maximumPoolSize為Integer.MAX_VALUE;keepAliveTime為60L;unit為TimeUnit.SECONDS;workQueue為SynchronousQueue(同步隊列)
通俗:當有新任務到來,則插入到SynchronousQueue中,由於SynchronousQueue是同步隊列,因此會在池中尋找可用線程來執行,若有可以線程則執行,若沒有可用線程則創建一個線程來執行該任務;若池中線程空閑時間超過指定大小,則該線程會被銷毀。
適用:執行很多短期異步的小程序或者負載較輕的服務器
2、newFixedThreadPool:
底層:返回ThreadPoolExecutor實例,接收參數為所設定線程數量nThread,corePoolSize為nThread,maximumPoolSize為nThread;keepAliveTime為0L(不限時);unit為:TimeUnit.MILLISECONDS;WorkQueue為:new LinkedBlockingQueueRunnable() 無解阻塞隊列
通俗:創建可容納固定數量線程的池子,每隔線程的存活時間是無限的,當池子滿了就不在添加線程了;如果池中的所有線程均在繁忙狀態,對於新任務會進入阻塞隊列中(無界的阻塞隊列)
適用:執行長期的任務,性能好很多
3、newSingleThreadExecutor
底層:FinalizableDelegatedExecutorService包裝的ThreadPoolExecutor實例,corePoolSize為1;maximumPoolSize為1;keepAliveTime為0L;unit為:TimeUnit.MILLISECONDS;workQueue為:new LinkedBlockingQueueRunnable() 無解阻塞隊列
通俗:創建只有一個線程的線程池,且線程的存活時間是無限的;當該線程正繁忙時,對於新任務會進入阻塞隊列中(無界的阻塞隊列)
適用:一個任務一個任務執行的場景
4、NewScheduledThreadPool:
底層:創建ScheduledThreadPoolExecutor實例,corePoolSize為傳遞來的參數,maximumPoolSize為Integer.MAX_VALUE;keepAliveTime為0;unit為:TimeUnit.NANOSECONDS;workQueue為:new DelayedWorkQueue() 一個按超時時間升序排序的隊列
通俗:創建一個固定大小的線程池,線程池內線程存活時間無限制,線程池可以支持定時及周期性任務執行,如果所有線程均處於繁忙狀態,對於新任務會進入DelayedWorkQueue隊列中,這是一種按照超時時間排序的隊列結構
適用:周期性執行任務的場景
最後給你說一下線程池任務執行流程:
當線程池小於corePoolSize時,新提交任務將創建一個新線程執行任務,即使此時線程池中存在空閑線程。
當線程池達到corePoolSize時,新提交任務將被放入workQueue中,等待線程池中任務調度執行
當workQueue已滿,且maximumPoolSizecorePoolSize時,新提交任務會創建新線程執行任務
當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理
當線程池中超過corePoolSize線程,空閑時間達到keepAliveTime時,關閉空閑線程
當設置allowCoreThreadTimeOut(true)時,線程池中corePoolSize線程空閑時間達到keepAliveTime也將關閉
java線程池的理解
線程池顧名思義是一個裝着”線程”的池子,,它包含了很多已經啟動好的並且處於睡眠狀態的線程.當你有請求時,就會直接使用池子裏面的線程而不用去創建.對於請求量很少的時候看起來沒多少作用…但是當系統使用人數多了..請求數量很多的時候就會為系統節約大量的資源,讓系統不去忙於線程的創建和銷毀,,,而讓系統更好的完成他的功能.
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/200960.html