摘要: 程序的運行,其本質上,是對系統資源(CPU、內存、磁盤、網絡等等)的使用。如何高效的使用這些資源是咱們編程優化演進的一個方向。今天說的線程池就是一種對CPU利用的優化手段。 網上有很多介紹如何使用線程池的文章,那我想說點什麼呢?我但願經過學習線程池原理,明白全部池化技術的基本設計思路。java
程序的運行,其本質上,是對系統資源(CPU、內存、磁盤、網絡等等)的使用。如何高效的使用這些資源是咱們編程優化演進的一個方向。今天說的線程池就是一種對CPU利用的優化手段。編程
網上有很多介紹如何使用線程池的文章,那我想說點什麼呢?我但願經過學習線程池原理,明白全部池化技術的基本設計思路。遇到其餘類似問題能夠解決。安全
池化技術網絡
前面提到一個名詞——池化技術,那麼到底什麼是池化技術呢?併發
池化技術簡單點來講,就是提早保存大量的資源,以備不時之需。在機器資源有限的狀況下,使用池化技術能夠大大的提升資源的利用率,提高性能等。ide
在編程領域,比較典型的池化技術有:函數
線程池、鏈接池、內存池、對象池等。工具
本文主要來介紹一下其中比較簡單的線程池的實現原理,但願讀者們能夠觸類旁通,經過對線程池的理解,學習並掌握全部編程中池化技術的底層原理。性能
建立一個線程學習
在Java的併發編程中,線程是十分重要的,在Java中,建立一個線程比較簡單:
publicclassApp{ publicstaticvoidmain(String[]args)throwsException{ newThread(newRunnable(){ @Override publicvoidrun(){ System.out.println("線程運行中"); } }).start(); } }
咱們經過建立一個線程對象,而且實現Runnable接口就能夠實現一個簡單的線程。能夠利用上多核CPU。當一個任務結束,當前線程就接收。
但不少時候,咱們不止會執行一個任務。若是每次都是如此的建立線程->執行任務->銷燬線程,會形成很大的性能開銷。
那可否一個線程建立後,執行完一個任務後,又去執行另外一個任務,而不是銷燬。這就是線程池。
這也就是池化技術的思想,經過預先建立好多個線程,放在池中,這樣能夠在須要使用線程的時候直接獲取,避免屢次重複建立、銷燬帶來的開銷。
線程池的簡單使用
如下代碼,是在Java中建立線程池:
importjava.util.concurrent.*;publicclassApp{ publicstaticvoidmain(String[]args)throwsException{ ExecutorServiceexecutorService=newThreadPoolExecutor(1,1, 60L,TimeUnit.SECONDS, newArrayBlockingQueue<>(10)); executorService.execute(newRunnable(){ @Override publicvoidrun(){ System.out.println("abcdefg"); } }); executorService.shutdown(); } }
Jdk提供給外部的接口也很簡單。直接調用ThreadPoolExecutor構造一個就能夠了,也能夠經過Executors靜態工廠構建,但通常不建議。
能夠看到,開發者想要在代碼中使用線程池仍是比較簡單的,這得益於Java給咱們封裝好的一系列API。可是,這些API的背後是什麼呢,讓咱們來揭開這個迷霧,看清線程池的本質。
線程池構造函數
一般,通常構造函數會反映出這個工具或這個對象的數據存儲結構。
若是把線程池比做一個公司。公司會有正式員工處理正常業務,若是工做量大的話,會僱傭外包人員來工做。
閒時就能夠釋放外包人員以減小公司管理開銷。一個公司由於成本關係,僱傭的人員始終是有最大數。
若是這時候還有任務處理不過來,就走需求池排任務。
acc : 獲取調用上下文
corePoolSize: 核心線程數量,能夠類比正式員工數量,常駐線程數量。
maximumPoolSize: 最大的線程數量,公司最多僱傭員工數量。常駐+臨時線程數量。
workQueue:多餘任務等待隊列,再多的人都處理不過來了,須要等着,在這個地方等。
keepAliveTime:非核心線程空閒時間,就是外包人員等了多久,若是尚未活幹,解僱了。
threadFactory: 建立線程的工廠,在這個地方能夠統一處理建立的線程的屬性。每一個公司對員工的要求不同,恩,在這裏設置員工的屬性。
handler:線程池拒絕策略,什麼意思呢?就是當任務實在是太多,人也不夠,需求池也排滿了,還有任務咋辦?默認是不處理,拋出異常告訴任務提交者,我這忙不過來了。
添加一個任務
接着,咱們看一下線程池中比較重要的execute方法,該方法用於向線程池中添加一個任務。
核心模塊用紅框標記了。
第一個紅框:workerCountOf方法根據ctl的低29位,獲得線程池的當前線程數,若是線程數小於corePoolSize,則執行addWorker方法建立新的線程執行任務;
第二個紅框:判斷線程池是否在運行,若是在,任務隊列是否容許插入,插入成功再次驗證線程池是否運行,若是不在運行,移除插入的任務,而後拋出拒絕策略。若是在運行,沒有線程了,就啓用一個線程。
第三個紅框:若是添加非核心線程失敗,就直接拒絕了。
這裏邏輯稍微有點複雜,畫了個流程圖僅供參考
接下來,咱們看看如何添加一個工做線程的?
添加worker線程
從方法execute的實現能夠看出:addWorker主要負責建立新的線程並執行任務,代碼以下(這裏代碼有點長,不要緊,也是分塊的,總共有5個關鍵的代碼塊):
第一個紅框:作是否可以添加工做線程條件過濾:
判斷線程池的狀態,若是線程池的狀態值大於或等SHUTDOWN,則不處理提交的任務,直接返回;
第二個紅框:作自旋,更新建立線程數量:
經過參數core判斷當前須要建立的線程是否爲核心線程,若是core爲true,且當前線程數小於corePoolSize,則跳出循環,開始建立新的線程
有人或許會疑問 retry 是什麼?這個是java中的goto語法。只能運用在break和continue後面。
接着看後面的代碼:
第一個紅框:獲取線程池主鎖。
線程池的工做線程經過Woker類實現,經過ReentrantLock鎖保證線程安全。
第二個紅框:添加線程到workers中(線程池中)。
第三個紅框:啓動新建的線程。
接下來,咱們看看workers是什麼。
一個hashSet。因此,線程池底層的存儲結構其實就是一個HashSet。
worker線程處理隊列任務
第一個紅框:是不是第一次執行任務,或者從隊列中能夠獲取到任務。
第二個紅框:獲取到任務後,執行任務開始前操做鉤子。
第三個紅框:執行任務。
第四個紅框:執行任務後鉤子。
這兩個鉤子(beforeExecute,afterExecute)容許咱們本身繼承線程池,作任務執行先後處理。
到這裏,源代碼分析到此爲止。接下來作一下簡單的總結。
總結
所謂線程池本質是一個hashSet。多餘的任務會放在阻塞隊列中。
只有當阻塞隊列滿了後,纔會觸發非核心線程的建立。因此非核心線程只是臨時過來打雜的。直到空閒了,而後本身關閉了。
線程池提供了兩個鉤子(beforeExecute,afterExecute)給咱們,咱們繼承線程池,在執行任務先後作一些事情。
線程池原理關鍵技術:鎖(lock,cas)、阻塞隊列、hashSet(資源池)
最後但願對你理解線程池有幫助。最後,留一個思考題,爲何線程池的底層數據接口採用HashSet來實現?
原文發佈時間爲:2018-10-08
本文做者:林灣村龍貓