1. 程式人生 > >java面試總躲不過的並發(一): 線程池ThreadPoolExecutor基礎梳理

java面試總躲不過的並發(一): 線程池ThreadPoolExecutor基礎梳理

進入 dot keepaliv apt 排序。 定位問題 代碼 微秒 image

本文核心:線程池ThreadPoolExecutor基礎梳理

一.實現多線程的方式

1.繼承Thread類,重寫其run方法

2.實現Runnable接口,實現run方法

3.實現Callable接口,實現call方法

由於Java的設計,只支持單繼承,但是支持多實現形式,所以一般面向接口開發,Runnable接口與Callable接口的區別在於Callable接口中的call方法是帶返回值的,其返回一個Future的異步類,我們可以通過Future的get方法獲取結果,如果線程還沒有執行完,get方法會阻塞。

Future還提供了很多有用的方法,像用於判斷線程是否執行完成的方法(isDone)

取消任務執行的方法 cancle()

二.線程的狀態

技術分享圖片

技術分享圖片

1. 新建狀態(New):新創建了一個線程對象。

2. 就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。

3. 運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。

4. 阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:

(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。

(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。

(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。

5. 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期

三.java並發開發中的最佳實踐

1.創建線程時給每個線程起一個名字,便於之後的問題查找

2.縮小並發區域

3.盡量使用JUC下的鎖

4.盡量使用並發集合而非同步集合

5.盡量使用並發鎖而非同步器鎖

6.如果可以不共享數據,多線程下盡量不共享

7.可以使用volatile修飾long和double

8.使用線程池

四.線程池

合理利用線程池能夠帶來三個好處。第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。第二:提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控

我們可以通過ThreadPoolExecutor來創建一個線程,ThreadPoolExecutor需要的參數如下:

線程池參數詳細說明如下:

corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閑的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啟動所有基本線程

runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。可以選擇以下幾個阻塞隊列。

1. ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。

2. LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。

3. SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。

4. PriorityBlockingQueue:一個具有優先級得無限阻塞隊列

maximumPoolSize(線程池最大大小):線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得註意的是如果使用了無界的任務隊列這個參數就沒什麽效果。

ThreadFactory:用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字,Debug和定位問題時非常又幫助

RejectedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麽必須采取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。以下是JDK1.5提供的四種策略。

1.AbortPolicy:直接拋出異常。

2.CallerRunsPolicy:只用調用者所在線程來運行任務。

3.DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。

4.DiscardPolicy:不處理,丟棄掉。

當然也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略。如記錄日誌或持久化不能處理的任務,如果某個任務被提交到一個已經關閉的Executor也會執行拒絕策略

keepAliveTime(線程活動保持時間):線程池的工作線程空閑後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。

TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)

五.線程池的工作流程

技術分享圖片

· 首先線程池判斷基本線程池是否已滿?沒滿,創建一個工作線程來執行任務。滿了,則進入下個流程。

· 其次線程池判斷工作隊列是否已滿?沒滿,則將新提交的任務存儲在工作隊列裏。滿了,則進入下個流程。

· 最後線程池判斷整個線程池是否已滿?沒滿,則創建一個新的工作線程來執行任務,滿了,則交給飽和策略來處理這個任務

今天先寫到這裏,有什麽不同見解記得關註我【不愛八阿哥】歡迎私信交流

轉自:https://www.toutiao.com/i6656553186586788365/

java面試總躲不過的並發(一): 線程池ThreadPoolExecutor基礎梳理