Take risks. If you win; you will be happy. If you lose; you will be wise.
冒險一試是值得的。如果赢了,你會得到快樂;如果輸了,你會得到智慧。
每日掏心話
誰不是從一個心地善良的孩子被現實折磨成一個心機深重的瘋子,從此開始,你的世界,與我無關,我的世界,你也隻配旁觀。
正文
1. Java的線程池
① 合理使用線程池的好處
Java的線程池是運用場景最多的并發架構,幾乎所有需要異步或者并發執行任務的程式都可以使用線程池。
合理使用線程池能帶來的好處:
降低資源消耗。
通過重複利用已經建立的線程降低線程建立的和銷毀造成的消耗。例如,工作線程Woker會無線循環擷取阻塞隊列中的任務來執行。
提高響應速度。
當任務到達時,任務可以不需要等到線程建立就能立即執行。
提高線程的可管理性。
線程是稀缺資源,Java的線程池可以對線程資源進行統一配置設定、調優和監控。
② 線程池的工作流程
一個新的任務到線程池時,線程池的處理流程如下:
- 線程池判斷核心線程池裡的線程是否都在執行任務。如果不是,建立一個新的工作線程來執行任務。如果核心線程池裡的線程都在執行任務,則進入下個流程。
- 線程池判斷阻塞隊列是否已滿。如果阻塞隊列沒有滿,則将新送出的任務存儲在阻塞隊列中。如果阻塞隊列已滿,則進入下個流程。
- 線程池判斷線程池裡的線程是否都處于工作狀态。如果沒有,則建立一個新的工作線程來執行任務。如果已滿,則交給飽和政策來處理這個任務。

線程池的核心實作類是ThreadPoolExecutor類,用來執行送出的任務。是以,任務送出到線程池時,具體的處理流程是由ThreadPoolExecutor類的execute()方法去完成的。
- 如果目前運作的線程少于corePoolSize,則建立新的工作線程來執行任務(執行這一步驟需要擷取全局鎖)。
- 如果目前運作的線程大于或等于corePoolSize,而且BlockingQueue未滿,則将任務加入到BlockingQueue中。
- 如果BlockingQueue已滿,而且目前運作的線程小于maximumPoolSize,則建立新的工作線程來執行任務(執行這一步驟需要擷取全局鎖)。
- 如果目前運作的線程大于或等于maximumPoolSize,任務将被拒絕,并調用RejectExecutionHandler.rejectExecution()方法。即調用飽和政策對任務進行處理。
工作線程(Worker)
線程池在建立線程時,會将線程封裝成工作線程Woker。Woker在執行完任務後,不是立即銷毀而是循環擷取阻塞隊列裡的任務來執行。
③ 線程池的建立(7個參數)
可以通過ThreadPoolExecutor來建立一個線程池:
new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
corePoolSize(線程池的基本大小):
- 送出一個任務到線程池時,線程池會建立一個新的線程來執行任務。注意:即使有空閑的基本線程能執行該任務,也會建立新的線程。
- 如果線程池中的線程數已經大于或等于corePoolSize,則不會建立新的線程。
- 如果調用了線程池的prestartAllCoreThreads()方法,線程池會提前建立并啟動所有基本線程。
- 搜尋公衆号頂級架構師背景回複“架構”,擷取一份驚喜禮包。
maximumPoolSize(線程池的最大數量):線程池允許建立的最大線程數。
- 阻塞隊列已滿,線程數小于maximumPoolSize便可以建立新的線程執行任務。
- 如果使用無界的阻塞隊列,該參數沒有什麼效果。
workQueue(工作隊列):用于儲存等待執行的任務的阻塞隊列。
- ArrayBlockingQueue:基于數組結構的有界阻塞隊列,按FIFO(先進先出)原則對任務進行排序。使用該隊列,線程池中能建立的最大線程數為maximumPoolSize。
- LinkedBlockingQueue:基于連結清單結構的無界阻塞隊列,按FIFO(先進先出)原則對任務進行排序,吞吐量高于ArrayBlockingQueue。使用該隊列,線程池中能建立的最大線程數為corePoolSize。靜态工廠方法 Executor.newFixedThreadPool()使用了這個隊列。
- SynchronousQueue:一個不存儲元素的阻塞隊列。添加任務的操作必須等到另一個線程的移除操作,否則添加操作一直處于阻塞狀态。靜态工廠方法 Executor.newCachedThreadPool()使用了這個隊列。
- PriorityBlokingQueue:一個支援優先級的無界阻塞隊列。使用該隊列,線程池中能建立的最大線程數為corePoolSize。
keepAliveTime(線程活動保持時間):線程池的工作線程空閑後,保持存活的時間。如果任務多而且任務的執行時間比較短,可以調大keepAliveTime,提高線程的使用率。
unit(線程活動保持時間的機關):可選機關有DAYS、HOURS、MINUTES、毫秒、微秒、納秒。
handler(飽和政策,或者又稱拒絕政策):當隊列和線程池都滿了,即線程池飽和了,必須采取一種政策處理送出的新任務。
- AbortPolicy:無法處理新任務時,直接抛出異常,這是預設政策。
- CallerRunsPolicy:用調用者所在的線程來執行任務。
- DiscardOldestPolicy:丢棄阻塞隊列中最靠前的一個任務,并執行目前任務。
- DiscardPolicy:直接丢棄任務。
threadFactory:建構線程的工廠類
總結:
1.常用的5個,核心池、最大池、空閑時間、時間的機關、阻塞隊列;另外兩個:拒絕政策、線程工廠類
2.常見線程池的建立參數如下。PS: CachedThreadPool核心池為0,最大池為Integer.MAX_VALUE,相當于隻使用了最大池;其他線程池,核心池與最大池一樣大,是以相當于隻用了核心池。
FixedThredPool: new ThreadExcutor(n, n, 0L, ms, new LinkedBlockingQueue<Runable>()
SingleThreadExecutor: new ThreadExcutor(1, 1, 0L, ms, new LinkedBlockingQueue<Runable>())
CachedTheadPool: new ThreadExcutor(0, max_valuem, 60L, s, new SynchronousQueue<Runnable>());
ScheduledThreadPoolExcutor: ScheduledThreadPool, SingleThreadScheduledExecutor.
3.如果使用的阻塞隊列為無界隊列,則永遠不會調用拒絕政策,因為再多的任務都可以放在隊列中。
4.SynchronousQueue是不存儲任務的,新的任務要麼立即被已有線程執行,要麼建立新的線程執行。
(搜尋公衆号Java知音,回複“2021”,送你一份Java面試題寶典)
④ 向線程池送出任務
使用ThreadPoolEXecutor.executor()方法來送出任務:
public void execute(Runnable command) {
// command為null,抛出NullPointerException
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 線程池中的線程數小于corePoolSize,建立新的線程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))// 建立工作線程
return;
c = ctl.get();
}
// 将任務添加到阻塞隊列,如果
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}// 阻塞隊列已滿,嘗試建立新的線程,如果超過maximumPoolSize,執行handler.rejectExecution()
else if (!addWorker(command, false))
reject(command);
}
⑤ 線程池的五種運作狀态
RUNNING : 該狀态的線程池既能接受新送出的任務,又能處理阻塞隊列中任務。
SHUTDOWN: 該狀态的線程池不能接收新送出的任務,但是能處理阻塞隊列中的任務。(政府服務大廳不在允許群衆拿号了,處理完手頭的和排隊的政務就下班。)
- 處于 RUNNING 狀态時,調用 shutdown()方法會使線程池進入到該狀态。
- 注意:finalize() 方法在執行過程中也會隐式調用shutdown()方法。
STOP: 該狀态的線程池不接受新送出的任務,也不處理在阻塞隊列中的任務,還會中斷正在執行的任務。(政府服務大廳不再進行服務了,拿号、排隊、以及手頭工作都停止了。)
- 線上程池處于 RUNNING 或 SHUTDOWN 狀态時,調用 shutdownNow() 方法會使線程池進入到該狀态;
TIDYING: 如果所有的任務都已終止,workerCount (有效線程數)=0 。
- 線程池進入該狀态後會調用 terminated() 鈎子方法進入TERMINATED 狀态。
TERMINATED: 在terminated()鈎子方法執行完後進入該狀态,預設terminated()鈎子方法中什麼也沒有做。
搜尋公衆号後端架構師背景回複“架構整潔”,擷取一份驚喜禮包。
⑥ 線程池的關閉(shutdown或者shutdownNow方法)
可以通過調用線程池的shutdown或者shutdownNow方法來關閉線程池:周遊線程池中工作線程,逐個調用interrupt方法來中斷線程。
shutdown方法與shutdownNow的特點:
- shutdown方法将線程池的狀态設定為SHUTDOWN狀态,隻會中斷空閑的工作線程。
- shutdownNow方法将線程池的狀态設定為STOP狀态,會中斷所有工作線程,不管工作線程是否空閑。
- 調用兩者中任何一種方法,都會使isShutdown方法的傳回值為true;線程池中所有的任務都關閉後,isTerminated方法的傳回值為true。
- 通常使用shutdown方法關閉線程池,如果不要求任務一定要執行完,則可以調用shutdownNow方法。
2. java線程池的調優以及監控
① 線程池的調優(線程池的合理配置)
先從以下幾個角度分析任務的特性:
- 任務的性質:CPU 密集型任務、IO 密集型任務和混合型任務。
- 任務的優先級:高、中、低。
- 任務的執行時間:長、中、短。
- 任務的依賴性:是否依賴其他系統資源,如資料庫連接配接。
任務性質不同的任務可以用不同規模的線程池分開處理。可以通過 Runtime.getRuntime().availableProcessors() 方法獲得目前裝置的 CPU 個數。
CPU 密集型任務配置 盡可能小的線程,如配置N^cpu+1個線程的線程池。
IO 密集型任務則由于線程并不是一直在執行任務,則配置盡可能多的線程,如2*N^cpu。
混合型任務 如果可以拆分,則将其拆分成一個 CPU 密集型任務和一個 IO 密集型任務。隻要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐率要高于串行執行的吞吐率;如果這兩個任務執行時間相差太大,則沒必要進行分解。
- 優先級不同的任務可以使用優先級隊列 PriorityBlockingQueue 來處理,它可以讓優先級高的任務先得到執行。但是,如果一直有高優先級的任務加入到阻塞隊列中,那麼低優先級的任務可能永遠不能執行。
- 執行時間不同的任務可以交給不同規模的線程池來處理,或者也可以使用優先級隊列,讓執行時間短的任務先執行。
- 依賴資料庫連接配接池的任務,因為線程送出 SQL 後需要等待資料庫傳回結果,線程數應該設定得較大,這樣才能更好的利用 CPU。
- 建議使用有界隊列,有界隊列能增加系統的穩定性和預警能力。可以根據需要設大一點,比如幾千。使用無界隊列,線程池的隊列就會越來越大,有可能會撐滿記憶體,導緻整個系統不可用。② 線程池的監控
可以通過線程池提供的參數讀線程池進行監控,有以下屬性可以使用:
- taskCount:線程池需要執行的任務數量,包括已經執行完的、未執行的和正在執行的。
- completedTaskCount:線程池在運作過程中已完成的任務數量,completedTaskCount <= taskCount。
- largestPoolSize:線程池曾經建立過的最大線程數量,通過這個資料可以知道線程池是否滿過。如等于線程池的最大大小,則表示線程池曾經滿了。
- getPoolSize: 線程池的線程數量。如果線程池不銷毀的話,池裡的線程不會自動銷毀,是以線程池的線程數量隻增不減。
- getActiveCount:擷取活動的線程數。
通過繼承線程池并重寫線程池的 beforeExecute,afterExecute 和 terminated 方法,我們可以在任務執行前,執行後和線程池關閉前幹一些事情。
如監控任務的平均執行時間,最大執行時間和最小執行時間等。這幾個方法線上程池裡是空方法,如:
protected void beforeExecute(Thread t, Runnable r) { }
3. Java線程池的常見問題
1. 講講Java的線程池
基礎講解:
- 以ThreadPoolExecutor為切入點,講解excute()方法中所展現的Java線程池運作流程。
- 工作線程Worker,它的循環工作特點
- 如何建立線程池:7個參數(重點在阻塞隊列和飽和政策)
進階:
- 線程池五個狀态的特點以及如何進行狀态之間的切換:running、shutdown、stop、tidying、terminated。
- 如何關閉線程:shutdown方法和shutdownNow方法的特點
- 線程池的調優(針對任務的不同特性 + 建議使用有界隊列)
- 線程池的監控參數以及可以重寫的方法。
兩種主要的線程池類型:普通的線程池ThreadPoolExecutor,支援延遲或周期性執行的任務的線程池ScheduledThreadPoolExcutor。
講解ThreadPoolExcutor中5個常用參數+2個不常用參數,包含的三種線程池:建立時的參數、運作的流程、各自适合的場景。
講解ScheduledThreadPoolExecutor的阻塞隊列的原理、如何更改任務的time。
提供了五種定義好的線程池,都可以通過Executors工具類去調用,比如Executors.newFixedThreadPool(12)
2. 具體的場景
3. 線程池如何進行調優?
4. 線程池中的核心參數
你還有什麼想要補充的嗎?PS:歡迎在留言區留下你的觀點,一起讨論提高。如果今天的文章讓你有新的啟發,歡迎轉發分享給更多人。
版權申明:内容來源網絡,版權歸原創者所有。除非無法确認,我們都會标明作者及出處,如有侵權煩請告知,我們會立即删除并表示歉意。謝謝!
歡迎加入後端架構師交流群,在背景回複“學習”即可。
最近面試BAT,整理一份面試資料《Java面試BAT通關手冊》,覆寫了Java核心技術、JVM、Java并發、SSM、微服務、資料庫、資料結構等等。在這裡,我為大家準備了一份2021年最新最全BAT等大廠Java面試經驗總結。别找了,想擷取史上最簡單的Java大廠面試題學習資料掃下方二維碼回複「面試」就好了猜你還想看阿裡、騰訊、百度、華為、京東最新面試題彙集Java版視訊管理系統,拿來即用(附源碼)
哈哈哈哈,16 歲高中生開發「粵語程式設計」項目,在 GitHub 火了!Windows 11 一夜全網曝光!附下載下傳+激活
嘿,你在看嗎?