- 一、定義
- 管理一組工作線程。
- 二、好處
- 1. 降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗,比如內存;
- 2. 提高響應速度。任務到達時,可以不需要等到線程創建就能執行;
- 3. 提高線程的可管理性。通過線程池,實現對線程的統一分配,調優和監控;比如可以避免無線創建線程引起OutOfMemoryError。
- 三、實現原理
- 當向線程池提交一個任務之後,線程池是如何處理這個任務的呢?
- 上圖就是線程池的主要處理流程;
- ThreadPoolExecutor 執行execute()方法的四種情況(流程):
- 1、如果當前運行的線程少於核心線程,則創建新線程來執行任務;(需要注意的是,此操作需要獲取全局鎖)
- 2、如果運行的線程等於或對於核心線程,則將任務加入工作隊列中;
- 3、如果工作隊列已滿,則創建新的線程(非核心線程)來處理任務;(需要獲取全局鎖)
- 4、如果創建線程超出最大線程數,則任務被拒絕,調用飽和策略;(RejectedExecutionHandler.rejectedExecution())
- 總結: 如此設計的原因?
- 爲了在執行execute()方法時,儘可能地避免獲取全局鎖;因爲獲取全局鎖是一個嚴重可伸縮瓶頸。
- 期望: 每次任務提交的時候,都是當前運行的線程數大於等於核心線程數,此時不用獲取全局鎖,即加入工作隊列。
- 當向線程池提交一個任務之後,線程池是如何處理這個任務的呢?
- 四、通過ThreadPoolExecutor創建線程池
- public ThreadPoolExecutor(
- int corePoolSize,
- int maximumPoolSize,
- long keepAliveTime,
- TimeUnit unit,
- BlockingQueue<Runnable> workQueue,
- ThreadFactory threadFactory,
- RejectedExecutionHandler defaultHandler)
- 1. corePoolSize
- 線程池的基本大小;如果要執行的任務數小於線程池基本大小,提交一個任務到線程池後,就會創建一個線程即便有空閒線程;否則,不創建,等待。
- 2. maximumPoolSize
- 線程池最大線程數。如果線程池中的線程數大於核心線程數並且隊列滿了,且線程數小於最大線程數,則會創建新的線程。
- 如果maximumPoolSize與corePoolSize相等,即是固定大小線程池。
- 如果使用無界隊列,該參數無效;
- 3. keepAliveTime
- 空閒線程存活時間。
- 默認情況下,當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用;當線程池中的線程空閒時,如果空閒時間等於keepAliveTime,線程會被銷燬,直到線程數等於核心線程數,避免浪費內存和句柄資源。
- 當ThreadPoolExecutor的allowCoreThreadTimeOut變量設置爲true時,核心線程超時後也會被回收。
- 如果任務多,並且每個任務的執行時間較短,增大時間,提高線程的利用率。
- 4. unit
- 時間單位
- 5. BlockingQueue<Runnable> workQueue
- 任務隊列,用於保存等待執行的任務的阻塞隊列。包括以下幾種:
- 1.ArrayBlockingQueue:一個基於數組結構的有界阻塞隊列,FIFO(先進先出)原則對元素進行排序;
- 2.LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,也是先進先出,一般情況下吞吐量高於ArrayBlockingQueue 。 FixedThreadPool()和SingleThreadExecutor()使用此隊列。
- 3.SynchronousQueue:一個不存儲元素的阻塞隊列。上一個線程執行移除操作後,之後的插入操作才能執行,否則會一直處於阻塞狀態。吞吐量高於LinkedBlockingQueue。CachedThreadPool()使用此隊列。
- 4.PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。
- 任務隊列,用於保存等待執行的任務的阻塞隊列。包括以下幾種:
- 6. ThreadFactory threadFactory
- 線程池創建線程使用的工廠;
- 使用線程池創建線程的時候可以給予更有意義的名稱,便於定位問題。
- 7. RejectedExecutionHandler defaultHandler
- 線程池對拒絕任務的處理策略;發生在隊列和線程池都滿了的時候。默認是AbortPolicy,表示無法處理新任務時拋出異常。
- 1.AbortPolicy : 直接拋出異常;
- 2.CallerRunsPolicy: 只用調用者所在線程來運行任務;
- 3.DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務;
- 4.DiscardPolicy:不處理,不丟棄;
- 線程池對拒絕任務的處理策略;發生在隊列和線程池都滿了的時候。默認是AbortPolicy,表示無法處理新任務時拋出異常。
- 自定義拒絕策略實現RejectedExecutionHandler接口,實現需要的場景。比如持久化拒絕的任務、記錄日誌等等。
- public ThreadPoolExecutor(
- 五、提交任務到線程池的方法
- (一)、 通過execute
- executorService.execute(newRunnable() {
- @Override
- public void run() {
- System.out.println("我要開始運行了");
- }
- });
- 提交不需要返回值的任務,無法判斷任務是否被線程池執行成功。
- executorService.execute(newRunnable() {
- (二)、 通過submit
- 1.通過提交Runnable
- Future future = executorService.submit(()->{});
- 2.通過提交Callable
- Future future = executorService.submit(() -> {
- return null;
- });
- Future future = executorService.submit(() -> {
- 3. Runnable 和 Callable 的區別
- 1. 非常相似,這兩個接口都表示可以由線程或ExecutorService同時執行的任務。
- 2. 不同之處是兩個接口內部執行的方法不同。
- Runnable
- public interface Runnable {
- public void run();
- }
- public interface Runnable {
- Callable
- public interface Callable{
- public Object call() throws Exception;
- }
- public interface Callable{
- Runnable
- - 可以看出,call()方法是有返回值的,而且可以引發異常。run沒有返回值,也不能引發異常(除非未經檢查的異常-RuntimeException的子類)。
- 4.如果執行的任務需要返回結果,使用Callable。
- 1.通過提交Runnable
- (三)、兩種執行方法的區別
- 1. execute 只能提交Runnable類型的任務;submit 除了可以提交Runnable類型的任務外,還可以提交Callable類型。
- 2. execute 直接拋出任務執行的異常;submit 會捕獲,可以返回一個Future類型的對象,通過這個Future對象可以判斷任務是否執行成功,通過get方法獲取返回值,get()方法會阻塞當前線程直到任務完成,使用get(long timeout,TimeUnit unit)方法會阻塞當前線程一段時間後立即返回,這時候任務可能沒有執行完。
- 3. submit 的頂級接口是ExecutorService;execute 的頂級接口是 Executor;
- (一)、 通過execute
- 六、獲取返回結果
- (一)、 兩種 invokeAny() 和 invokeAll()
- (二)、invokeAny
- 1. 當任意一個任務得到結果後,會調用interrupt方法將其他的任務中斷;
- 2. 部分任務失敗,會使用第一個成功的任務返回的結果;
- 3. 任務全部失敗了,拋出Execption,invokeAny 方法將拋出ExecutionException。
- (三)、invokeAll 返回所有任務的執行結果,該方法的執行效果也是阻塞執行的,要把所有的結果都取回時再繼續向下執行。
- 七、關閉線程池
- 1、shutdown()
- 1. 將線程池的狀態設置成ShutDown;
- 2. 中斷所有沒有正在執行任務的線程。在終止前允許執行以前提交的任務;
- 2、shutdownNow()
- 1. 將線程池的狀態設置成Stop;
- 2. 阻止等待任務的啓動並試圖停止當前正在執行的任務,並返回等待執行任務的列表;不允許執行以前提交的任務。
- 1、2 兩個方法的原理:
- 遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。
- 調用1、2方法後,isShutdown 方法返回true。當所有任務都已關閉,才表示線程池關閉成功,調用isTerminaed方法返回true。
- 3、awaitTermination()
- 1. 接收timeout和TimeUnit兩個參數,用於設定超時時間及單位。當等待超過設定時間時,會監測ExecutorService是否已經
- 關閉,若關閉則返回true,否則返回false。一般情況下會和shutdown方法組合使用。
- 在實際使用過程中, 使用shutdown()關閉,回收資源。如果有必要,可以在其後執行shutdownNow(),取消所有遺留的任務。
- 1、shutdown()
- 八、配置線程池
- 角度:
- 1. 任務的性質: CPU密集型任務、IO密集型任務和混合型任務。
- 2. 任務的優先級: 高、中與低;
- 3. 任務的執行時間: 長、短;
- 4. 任務的依賴性: 是否依賴其他系統資源,如數據庫連接。
- 選擇:
- 性質不同的任務可以用不同規模的線程池分開處理;
- CPU 密集型任務應配置儘可能小的線程,如cpu數+1 的線程池;
- IO密集型任務線程並不是一直在執行,則應配置極可能多的線程;
- 優先級不同的任務可以使用優先級隊列PriorityBlockingQueue來處理,它可以讓優先級高的任務先執行;
- 如果一直有優先級高的任務提交到隊列裏,那麼優先級低的任務可能永遠不能執行。
- 依賴數據庫連接的任務,因爲線程提交SQL後需要等待數據庫返回結果,等待的時間越長,則CPU空閒時間就越長,那麼線程數應該設置的越大,這樣才能更好低利用CPU。
- 建議:
- 有界隊列;有界隊列可以增加系統的穩定性和預警能力。
- 體現:
- 任務線程池的隊列和線程池滿了,不斷拋出拋棄任務的異常,通過排查是數據庫出現問題,導致執行sql的速度變慢,由於後臺任務線程池裏的任務都是需要向數據庫插入數據的和查詢,所以導致線程池裏的工作線程全部阻塞,任務積壓在線程池裏。如果是無界隊列,線程池中的隊列越來越多,可能撐爆內存,導致系統不可用。
- 角度:
- 八、例子
- ExecutorService executorService = Executors.newSingleThreadExecutor();
- Set<Callable<String>> callables = newHashSet<Callable<String>>();
- callables.add(() -> {
- return "Task1";
- }});
- callables.add(() -> {
- callables.add(() -> {
- return "Task2";
- }});
- callables.add(() -> {
- List<Future<String>> futures =executorService.invokeAll(callables);
- for(Future<String> future : futures){
- System.out.println("future.get = " + future.get());
- }
- for(Future<String> future : futures){
- executorService.shutdown();
- while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
- System.out.println("線程池沒有關閉");
- }
- Set<Callable<String>> callables = newHashSet<Callable<String>>();
- ExecutorService executorService = Executors.newSingleThreadExecutor();
線程池_初步認識_01
線程池_初步認識_01
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.