這篇文章主要講解了“Java 線程池的優(yōu)點和實現(xiàn)原理”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java 線程池的優(yōu)點和實現(xiàn)原理”吧!
成都創(chuàng)新互聯(lián)是網(wǎng)站建設技術企業(yè),為成都企業(yè)提供專業(yè)的成都網(wǎng)站設計、成都做網(wǎng)站,網(wǎng)站設計,網(wǎng)站制作,網(wǎng)站改版等技術服務。擁有10年豐富建站經(jīng)驗和眾多成功案例,為您定制適合企業(yè)的網(wǎng)站。10年品質,值得信賴!
Java 系統(tǒng)的運行歸根到底是程序的運行,程序的運行歸根到底是代碼的執(zhí)行,代碼的執(zhí)行歸根到底是虛擬機的執(zhí)行,虛擬機的執(zhí)行其實就是操作系統(tǒng)的線程在執(zhí)行,并且會占用一定的系統(tǒng)資源,如CPU、內存、磁盤、網(wǎng)絡等等。所以,如何高效的使用這些資源就是程序員在平時寫代碼時候的一個努力的方向。本文要說的線程池就是一種對 CPU 利用的優(yōu)化手段。
線程池,百度百科是這么解釋的:
線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然后在創(chuàng)建線程后自動啟動這些任務。線程池線程都是后臺線程。每個線程都使用默認的堆棧大小,以默認的優(yōu)先級運行,并處于多線程單元中。如果某個線程在托管代碼中空閑(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間后創(chuàng)建另一個輔助線程但線程的數(shù)目永遠不會超過最大值。超過最大值的線程可以排隊,但他們要等到其他線程完成后才啟動。
線程池,其實就是維護了很多線程的池子,類似這樣的池化技術還有很多的,例如:HttpClient 連接池、數(shù)據(jù)庫連接池、內存池等等。
在 Java 并發(fā)編程框架中的線程池是運用場景最多的技術,幾乎所有需要異步或并發(fā)執(zhí)行任務的程序都可以使用線程池。在開發(fā)過程中,合理地使用線程池能夠帶來至少以下4個好處。
第一:降低資源消耗。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗;
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創(chuàng)建就能立即執(zhí)行;
第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一分配、調優(yōu)和監(jiān)控。
第四:提供更強大的功能。比如延時定時線程池;
當向線程池提交一個任務之后,線程池是如何處理這個任務的呢?下面就先來看一下它的主要處理流程。先來看下面的這張圖,然后我們一步一步的來解釋。
當使用者將一個任務提交到線程池以后,線程池是這么執(zhí)行的:
①首先判斷核心的線程數(shù)是否已滿,如果沒有滿,那么就去創(chuàng)建一個線程去執(zhí)行該任務;否則請看下一步
②如果線程池的核心線程數(shù)已滿,那么就繼續(xù)判斷任務隊列是否已滿,如果沒滿,那么就將任務放到任務隊列中;否則請看下一步
③如果任務隊列已滿,那么就判斷線程池是否已滿,如果沒滿,那么就創(chuàng)建線程去執(zhí)行該任務;否則請看下一步;
④如果線程池已滿,那么就根據(jù)拒絕策略來做出相應的處理;
上面的四步其實就已經(jīng)將線程池的執(zhí)行原理描述結束了。如果不明白沒有關系,先一步一步往下看,上面涉及到的線程池的專有名詞都會詳細的介紹到。
我們在平時的開發(fā)中,線程池的使用基本都是基于ThreadPoolExexutor 類,他的繼承體系是這樣子的:
image-20210322133058425
那既然說在使用中都是基于 ThreadPoolExexutor 的那么我們就重點分析這個類。
至于他構造體系中的其他的類或者是接口中的屬性,這里就不去截圖了,完全沒有必要。小伙伴如果實在想看就自己去打開代碼看一下就行了。
在《阿里巴巴 java 開發(fā)手冊》中指出了線程資源必須通過線程池提供,不允許在應用中自行顯示的創(chuàng)建線程,這樣一方面是線程的創(chuàng)建更加規(guī)范,可以合理控制開辟線程的數(shù)量;另一方面線程的細節(jié)管理交給線程池處理,優(yōu)化了資源的開銷。
其原文描述如下:
在ThreadPoolExecutor類中提供了四個構造方法,但是他的四個構造器中,實際上最終都會調用同一個構造器,只不過是在另外三個構造器中,如果有些參數(shù)不傳ThreadPoolExecutor會幫你使用默認的參數(shù)。所以,我們直接來看這個完整參數(shù)的構造器,來徹底剖析里面的參數(shù)。
public class ThreadPoolExecutor extends AbstractExecutorService { ...... public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0){ throw new IllegalArgumentException(); } if (workQueue == null || threadFactory == null || handler == null){ throw new NullPointerException(); } this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; } }
主要參數(shù)就是下面這幾個:
corePoolSize:線程池中的核心線程數(shù),包括空閑線程,也就是核心線程數(shù)的大?。?/p>
maximumPoolSize:線程池中允許的最多的線程數(shù),也就是說線程池中的線程數(shù)是不可能超過該值的;
keepAliveTime:當線程池中的線程數(shù)大于 corePoolSize 的時候,在超過指定的時間之后就會將多出 corePoolSize 的的空閑的線程從線程池中刪除;
unit:keepAliveTime 參數(shù)的單位(常用的秒為單位);
workQueue:用于保存任務的隊列,此隊列僅保持由 executor 方法提交的任務 Runnable 任務;
threadFactory:線程池工廠,他主要是為了給線程起一個標識。也就是為線程起一個具有意義的名稱;
handler:拒絕策略
阻塞隊列
workQueue 有多種選擇,在 JDK 中一共提供了 7 中阻塞對列,分別為:
ArrayBlockingQueue :一個由數(shù)組結構組成的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。默認情況下不保證訪問者公平地訪問隊列 ,所謂公平訪問隊列是指阻塞的線程,可按照阻塞的先后順序訪問隊列。非公平性是對先等待的線程是不公平的,當隊列可用時,阻塞的線程都可以競爭訪問隊列的資格。
LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。此隊列的默認和最大長度為Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序。
PriorityBlockingQueue :一個支持優(yōu)先級排序的無界阻塞隊列。(雖然此隊列邏輯上是無界的,但是資源被耗盡時試圖執(zhí)行 add 操作也將失敗,導致 OutOfMemoryError)
DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)的無界阻塞隊列。元素的一個無界阻塞隊列,只有在延遲期滿時才能從中提取元素
SynchronousQueue:一個不存儲元素的阻塞隊列。一種阻塞隊列,其中每個插入操作必須等待另一個線程的對應移除操作 ,反之亦然。(SynchronousQueue 該隊列不保存元素)
LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。相對于其他阻塞隊列LinkedTransferQueue多了tryTransfer和transfer方法。
LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。是一個由鏈表結構組成的雙向阻塞隊列
在以上的7個隊列中,線程池中常用的是ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue,隊列中的常用的方法如下:
關于阻塞隊列,介紹到這里也就基本差不多了。
線程池工廠
線程池工廠,就像上面已經(jīng)介紹的,目的是為了給線程起一個有意義的名字。用起來也非常的簡單,只需要實現(xiàn)ThreadFactory接口即可
public class CustomThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("我是你們自己定義的線程名稱"); return thread; } }
具體的使用就不去廢話了。
拒絕策略
線程池有四種默認的拒絕策略,分別為:
AbortPolicy:這是線程池默認的拒絕策略,在任務不能再提交的時候,拋出異常,及時反饋程序運行狀態(tài)。如果是比較關鍵的業(yè)務,推薦使用此拒絕策略,這樣子在系統(tǒng)不能承載更大的并發(fā)量的時候,能夠及時的通過異常發(fā)現(xiàn);
DiscardPolicy:丟棄任務,但是不拋出異常。如果線程隊列已滿,則后續(xù)提交的任務都會被丟棄,且是靜默丟棄。這玩意不建議使用;
DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新提交被拒絕的任務。這玩意不建議使用;
CallerRunsPolicy:如果任務添加失敗,那么主線程就會自己調用執(zhí)行器中的 executor 方法來執(zhí)行該任務。這玩意不建議使用;
也就是說關于線程池的拒絕策略,最好使用默認的。這樣能夠及時發(fā)現(xiàn)異常。如果上面的都不能滿足你的需求,你也可以自定義拒絕策略,只需要實現(xiàn) RejectedExecutionHandler 接口即可
public class CustomRejection implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("你自己想怎么處理就怎么處理"); } }
看到這里,我們再來畫一張圖來總結和概括下線程池的執(zhí)行示意圖:
詳細的執(zhí)行過程全部在圖中說明了。
在 java 中,有兩個方法可以將任務提交到線程池,分別是submit和execute。
execute 方法
execute()方法用于提交不需要返回值的任務,所以無法判斷任務是否被線程池執(zhí)行成功。
void execute(Runnable command);
通過以下代碼可知 execute() 方法輸入的任務是一個Runnable類的實例。
executorService.execute(()->{ System.out.println("ThreadPoolDemo.execute"); });
submit 方法
submit()方法用于提交需要返回值的任務。
Future<?> submit(Runnable task);
線程池會返回一個future類型的對象,通過這個 future 對象可以判斷任務是否執(zhí)行成功,并且可以通過future的get()方法來獲取返回值,get() 方法會阻塞當前線程直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間后立即返回,這時候有可能任務沒有執(zhí)行完。
Future<?> submit = executorService.submit(() -> { System.out.println("ThreadPoolDemo.submit"); });
其實,如果優(yōu)雅的關閉線程池是一個令人頭疼的問題,線程開啟是簡單的,但是想要停止卻不是那么容易的。通常而言, 大部分程序員都是使用 jdk 提供的兩個方法來關閉線程池,他們分別是:shutdown 或 shutdownNow;
通過調用線程池的 shutdown 或 shutdownNow 方法來關閉線程池。它們的原理是遍歷線程池中的工作線程,然后逐個調用線程的 interrupt 方法來中斷線程(PS:中斷,僅僅是給線程打上一個標記,并不是代表這個線程停止了,如果線程不響應中斷,那么這個標記將毫無作用),所以無法響應中斷的任務可能永遠無法終止。
但是它們存在一定的區(qū)別,shutdownNow首先將線程池的狀態(tài)設置成 STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務的線程,并返回等待執(zhí)行任務的列表,而 shutdown 只是將線程池的狀態(tài)設置成SHUTDOWN狀態(tài),然后中斷所有沒有正在執(zhí)行任務的線程。
只要調用了這兩個關閉方法中的任意一個,isShutdown 方法就會返回 true。當所有的任務都已關閉后,才表示線程池關閉成功,這時調用isTerminaed方法會返回 true。至于應該調用哪一種方法來關閉線程池,應該由提交到線程池的任務特性決定,通常調用 shutdown方法來關閉線程池,如果任務不一定要執(zhí)行完,則可以調用 shutdownNow 方法。
這里推薦使用穩(wěn)妥的 shutdownNow 來關閉線程池,至于更優(yōu)雅的方式我會在以后的并發(fā)編程設計模式中的兩階段終止模式中會再次詳細介紹。
為什么叫合理的參數(shù),那不合理的參數(shù)是什么樣子的?在我們創(chuàng)建線程池的時候,里面的參數(shù)該如何設置才能稱之為合理呢?其實這是有一定的依據(jù)的,我們先來看一下以下的創(chuàng)建的方式:
ExecutorService executorService = new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), r -> { Thread thread = new Thread(r); thread.setName("線程池原理講解"); return thread; });
你說他合理不合理?我也不知道,因為我們沒有參考的依據(jù),在實際的開發(fā)中,我們需要根據(jù)任務的性質(IO是否頻繁?)來決定我們創(chuàng)建的核心的線程數(shù)的大小,實際上可以從以下的一個角度來分析:
任務的性質:CPU密集型任務、IO密集型任務和混合型任務;
任務的優(yōu)先級:高、中和低;
任務的執(zhí)行時間:長、中和短;
任務的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接;
性質不同的任務可以用不同規(guī)模的線程池分開處理。分為CPU密集型和IO密集型。
CPU密集型任務應配置盡可能小的線程,如配置 Ncpu+1個線程的線程池。(可以通過Runtime.getRuntime().availableProcessors()來獲取CPU物理核數(shù))
IO密集型任務線程并不是一直在執(zhí)行任務,則應配置盡可能多的線程,如 2*Ncpu。
混合型的任務,如果可以拆分,將其拆分成一個CPU密集型任務一個IO密集型任務,只要這兩個任務執(zhí)行的時間相差不是太大,那么分解后執(zhí)行的吞吐量將高于串行執(zhí)行的吞吐量。
如果這兩個任務執(zhí)行時間相差太大,則沒必要進行分解??梢酝ㄟ^ Runtime.getRuntime().availableProcessors() 方法獲得當前設備的CPU個數(shù)。
優(yōu)先級不同的任務可以使用優(yōu)先級隊列 PriorityBlockingQueue來處理。它可以讓優(yōu)先級高的任務先執(zhí)行(注意:如果一直有優(yōu)先級高的任務提交到隊列里,那么優(yōu)先級低的任務可能永遠不能執(zhí)行)
執(zhí)行時間不同的任務可以交給不同規(guī)模的線程池來處理,或者可以使用優(yōu)先級隊列,讓執(zhí)行時間短的任務先執(zhí)行。依賴數(shù)據(jù)庫連接池的任務,因為線程提交SQL后需要等待數(shù)據(jù)庫返回結果,等待的時間越長,則 CPU 空閑時間就越長,那么線程數(shù)應該設置得越大,這樣才能更好地利用CPU。
建議使用有界隊列。有界隊列能增加系統(tǒng)的穩(wěn)定性和預警能力,可以根據(jù)需要設大一點。方式因為提交的任務過多而導致 OOM
本文主要介紹的是線程池的實現(xiàn)原理以及一些使用技巧,在實際開發(fā)中,線程池可以說是稍微高級一點的程序員的必備技能。所以掌握好線程池這門技術也是重中之重!
感謝各位的閱讀,以上就是“Java 線程池的優(yōu)點和實現(xiàn)原理”的內容了,經(jīng)過本文的學習后,相信大家對Java 線程池的優(yōu)點和實現(xiàn)原理這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關知識點的文章,歡迎關注!
當前題目:Java線程池的優(yōu)點和實現(xiàn)原理
網(wǎng)頁網(wǎng)址:http://jinyejixie.com/article0/jjhooo.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站、虛擬主機、網(wǎng)站導航、網(wǎng)站維護、定制開發(fā)、網(wǎng)頁設計公司
聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)