JUC系列(九)| ThreadPool 线程池
多線程一直Java開發中的難點,也是面試中的常客,趁著還有時間,打算鞏固一下JUC方面知識,我想機會隨處可見,但始終都是留給有準備的人的,希望我們都能加油!!!
沉下去,再浮上來,我想我們會變的不一樣的。
🍟線程池介紹
1)什么是線程池?
線程池(英語:thread pool):一種線程使用模式。由系統維護的容納線程的容器,由CLR控制的所有AppDomain共享。線程池可用于執行任務、發送工作項、處理異步 I/O、代表其他線程等待以及處理計時器。
2)為什么要使用線程池?
痛點:
原因:
線程池維護著多個線程,等待著監督管理者分配可并發執行的任務。這避免了在處理短時間任務時創建與銷毀線程的代價。線程池不僅能夠保證內核的充分利用,還能防止過分調度。
線程池的優勢:
線程池做的工作只要是控制運行的線程數量,處理過程中將任務放入隊列,然后在線程創建后啟動這些任務,如果線程數量超過了最大數量, 超出數量的線程排隊等候,等其他線程執行完畢,再從隊列中取出任務來執行。
另外在我們常常看的阿里巴巴Java開發手冊也有提到:
【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
說明:使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。
3)特點
降低資源消耗:通過重復利用已創建的線程降低線程創建和銷毀造成的銷耗。
提高響應速度:當任務到達時,任務可以不需要等待線程創建就能立即執行。
提高線程的可管理性: 線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
4)架構
Java 中的線程池是通過 Executor 框架實現的,該框架中用到了 Executor, ExecutorService,ThreadPoolExecutor, 這幾個類
另外還有Executors這個方便封裝的工具類。但是不建議使用😂
5)線程池參數說明
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)- 當提交一個任務時,如果當前核心線程池的線程個數沒有達到 corePoolSize,則會創建新的線程來執行所提交的任務,即使當前核心線程池有空閑的線程。如果當前核心線程池的線程個數已經達到了 corePoolSize,則不再重新創建線程。
- 即當阻塞隊列已滿時,并且當前線程池線程個數沒有超過 maximumPoolSize 的話,就會創建新的線程來執行任務。
- 如果當前線程池的線程個數已經超過了 corePoolSize,并且線程空閑時間超過了 keepAliveTime 的話,就會將這些空閑線程銷毀,這樣可以盡可能降低系統資源消耗。
線程池中,有三個重要的參數,決定影響了拒絕策略:
6)拒絕策略
AbortPolicy: 丟棄任務,并拋出拒絕執行 RejectedExecutionException 異常信息。線程池默認的拒絕策略。必須處理好拋出的異常,否則會打斷當前的執行流程,影響后續的任務執行。
CallerRunsPolicy: 當觸發拒絕策略,只要線程池沒有關閉的話,則使用調用線程直接運行任務。一般并發比較小,性能要求不高,不允許失敗。但是,由 于調用者自己運行任務,如果任務提交速度過快,可能導致程序阻塞,性能效 率上必然的損失較大
DiscardPolicy: 直接丟棄.
DiscardOldestPolicy: 觸發拒絕策略時,只要線程池沒有關閉的話,丟棄阻塞隊列 workQueue中最老的任務,并將新任務加入
🧇線程池種類
Jav有4種默認線程池,分別是:
2.1、 newCachedThreadPool
作用:創建一個可緩存線程池,此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(或者說JVM)能夠創建的最大線程大小。如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程.
比較適合用于創建一個可無限擴大的線程池,執行時間較短,任務多的場景。
特點:
創建方式:
/*** 可緩存線程池* @return*/ public static ExecutorService newCachedThreadPool() {/*** corePoolSize 線程池的核心線程數* maximumPoolSize 能容納的最大線程數* keepAliveTime 空閑線程存活時間* unit 存活的時間單位* workQueue 存放提交但未執行任務的隊列* threadFactory 創建線程的工廠類:可以省略* handler 等待隊列滿后的拒絕策略:可以省略*/return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue<>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); }2.2、newFixedThreadPool
作用:
用來創建一個可重用固定線程數的線程池,以共享的無界隊列(LinkedBlockingQueue)方式來運行這些線程。
適用于預測并發壓力的情況下,對線程數做出限制;或者對線程數有嚴格限制的場景。
特點:
創建方式:
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); }2.3、newSingleThreadExecutor
作用:
創建一個使用單個 worker 線程的 Executor,以無界隊列(LinkedBlockingQueue)方式來運行該線程。超出的任務都將存儲在隊列中,等待執行。
適用于需要保證順序執行各個任務,并且在任意時間點,不會同時有多個線程的場景
特點:
線程池中最多執行 1 個線程,之后提交的線程活動將會排在隊列中以此 執行
創建方式:
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())); }2.4、newScheduledThreadPool
作用:
創建一個 corePoolSize 為傳入參數,最大線程數為(Integer.MAX_VALUE)的線程池,此線程池支持定時以及周期性執行任務的需求。
比較適用于需要多個后臺線程執行周期任務的場景,某個時候要收集日志了,發送推送消息拉等等都很合適😂
特點:
可定時或延遲執行線程活動~~(我拿這個模擬過動態定時,意思是給定個未來某個時間,然后到那個時間點就執行)~~
創建方式:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue(), threadFactory); }public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler); }🍖入門案例
火車站 3 個售票口, 10 個用戶買票
import java.util.concurrent.*;/*** 入門案例*/ public class ThreadPoolDemo1 {/*** 火車站 3 個售票口, 10 個用戶買票** @param args*/public static void main(String[] args) {//定時線程次:線程數量為 3---窗口數為 3ExecutorService threadService = new ThreadPoolExecutor(3,3,60L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());try {//10 個人買票for (int i = 1; i <= 10; i++) {threadService.execute(() -> {try {System.out.println(Thread.currentThread().getName() + " 窗口,開始賣票");Thread.sleep(500);System.out.println(Thread.currentThread().getName() + " 窗口買票結束");} catch (Exception e) {e.printStackTrace();}});}} catch (Exception e) {e.printStackTrace();} finally {//完成后結束threadService.shutdown();}} } /**pool-1-thread-3 窗口,開始賣票pool-1-thread-2 窗口,開始賣票pool-1-thread-1 窗口,開始賣票pool-1-thread-3 窗口買票結束pool-1-thread-2 窗口買票結束pool-1-thread-1 窗口買票結束....*/🍤線程池底層工作原理(重要)
public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();//如果線程池的線程個數少于corePoolSize則創建新線程執行當前任務if (workerCountOf(c) < corePoolSize) {//執行addworker,創建一個核心線程,創建失敗重新獲取ctlif (addWorker(command, true))return;c = ctl.get();} //如果工作線程數大于核心線程數,判斷線程池的狀態是否為running,并且可以添加進隊列 //如果線程池不是running狀態,則執行拒絕策略,(還是會調用一次addworker)if (isRunning(c) && workQueue.offer(command)) {//再次獲取ctl,進行雙重檢索int recheck = ctl.get();//如果線程池是不是處于RUNNING的狀態,那么就會將任務從隊列中移除, //如果移除失敗,則會判斷工作線程是否為0 ,如果過為0 就創建一個非核心線程 //如果移除成功,就執行拒絕策略,因為線程池已經不可用了;if (! isRunning(recheck) && remove(command))//為給定的命令調用被拒絕的執行處理程序reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}//檢查是否可以根據當前池狀態和給定界限(核心或最大值)添加新的工作線程。 //如果是這樣,則相應地調整工作人員數量,并且如果可能,將創建并啟動一個新工作人員,將 firstTask 作為其第一個任務運行。 //如果池已停止或有資格關閉,則此方法返回 false。 //如果線程工廠在詢問時未能創建線程,它也會返回 false。 //如果線程創建失敗,要么是由于線程工廠返回 null,要么是由于異常(通常是 Thread.start() 中的 OutOfMemoryError),我們會干凈利落地回滾。else if (!addWorker(command, false))// 為給定的命令調用被拒絕的執行處理程序reject(command); }上圖取自:線程池ThreadPoolExecutor實現原理 作者:你聽___
execute執行邏輯:
- 如果正在運行的線程數量小于 corePoolSize,那么馬上創建線程運行這個任務;
- 如果正在運行的線程數量大于或等于 corePoolSize,那么將這個任務放入 隊列;
- 如果這個時候隊列滿了且正在運行的線程數量還小于 maximumPoolSize,那么還是要創建非核心線程立刻運行這個任務;
- 如果隊列滿了且正在運行的線程數量大于或等于 maximumPoolSize,那么線程 池會啟動飽和拒絕策略來執行。
🍦注意事項
創建線程池推薦適用 ThreadPoolExecutor 及其 7 個參數手動創建
為什么不使用Executors創建?
🍕自言自語
最近又開始了JUC的學習,感覺Java內容真的很多,但是為了能夠走的更遠,還是覺得應該需要打牢一下基礎。
最近在持續更新中,如果你覺得對你有所幫助,也感興趣的話,關注我吧,讓我們一起學習,一起討論吧。
你好,我是博主寧在春,Java學習路上的一顆小小的種子,也希望有一天能扎根長成蒼天大樹。
希望與君共勉😁
我們:待別時相見時,都已有所成。
參考:
線程池ThreadPoolExecutor實現原理
https://www.zhihu.com/question/23212914/answer/245992718 作者:大閑人柴毛毛
阿里巴巴開發手冊
總結
以上是生活随笔為你收集整理的JUC系列(九)| ThreadPool 线程池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java | 设计模式-适配器模式
- 下一篇: 为什么阿里巴巴开发手册中强制要求 POJ