java多執行緒:執行緒池原理、阻塞佇列
阿新 • • 發佈:2020-10-08
# 一、執行緒池定義和使用
jdk 1.5 之後就引入了執行緒池。 ## 1.1 定義
從上面的空間切換看得出來,執行緒是稀缺資源,它的建立與銷燬是一個相對偏重且耗資源的操作,而Java執行緒依賴於核心執行緒,建立執行緒需要進行作業系統狀態切換。為避免資源過度消耗需要設法重用執行緒執行多個任務。執行緒池就是一個執行緒快取,負責對執行緒進行統一分配、調優與監控。(資料庫連線池也是一樣的道理) **什麼時候使用執行緒池?** 單個任務處理時間比較短;需要處理的任務數量很大。 **執行緒池優勢?** * 重用存在的執行緒,減少執行緒建立、消亡的開銷,提高效能、提高響應速度。 * 當任務到達時,任務可以不需要等到執行緒建立就能立即執行。 * 提高執行緒的可管理性,可統一分配,調優和監控。 ## 1.2 執行緒池在 jdk 已有的實現
這裡我們用固定執行緒池來測試,傳入核心執行緒數為 5,最大數量自然就也是 5, ```java public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(5); try { //模擬10個顧客辦理業務 for (int i = 0; i < 10; i++){ //execute 執行方法,傳入引數為實現了 Runnable 介面的類 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"號執行緒辦理業務"); }); } } catch (Exception e){ e.printStackTrace(); } finally { threadPool.shutdown(); } } ``` 其中,execute 方法就是將**任務提交**的方法,我們用 lambda 表示式給 execute 方法傳入了引數,實際上相當於一個完整的實現了 Runnable 介面的類。 執行結果:
# 二、執行緒池底層原理
## 2.1 執行緒池執行邏輯
處理的流程核心就 execute() 方法,他接收一個實現了 Runnable 介面的任務,決定對這個任務的處理策略。
有些時候,我們並不希望拒絕策略是直接丟擲異常,那麼 jdk 裡面提供的預設拒絕策略有 4 種,他們體現在程式碼中就是 ThreadPoolExecutor 的四個靜態內部類:
和**一個正常的執行緒的生命週期**區別開,這個是**執行緒池裡執行緒**的狀態。 1. Running,能接受新任務以及處理已新增的任務; 2. Shutdown,不接受新任務,可以處理已經新增的任務,也就是不能再呼叫execute或者submit了; 3. Stop,不接受新任務,不處理已經新增的任務,並且中斷正在處理的任務; 4. Tidying,所有的任務已經終止,CTL記錄的任務數量為0,CTL負責記錄執行緒池的執行狀態與活動執行緒數量; 5. Terminated,執行緒池徹底終止,則執行緒池轉變為terminated的狀態。
# 三、阻塞佇列
執行緒池裡的 BlockingQueue,**阻塞佇列**,事實上在消費者生產者問題裡的管程法實現,我們的策略也是類似阻塞佇列的,用它來做一個快取池的作用。 **阻塞佇列**:任意時刻,不管併發有多高,永遠保證**只有一個執行緒能夠進行佇列的入隊或出隊操作**。也就意味著他是能夠保證執行緒安全的。 另外,阻塞佇列分為有界和無界佇列,理論上來說一個是佇列的size有固定,另一個是無界的。對於有界佇列來說,如果佇列存滿,只能出隊了,入隊操作就只能阻塞。 在 juc 包裡,阻塞佇列的實現有很多: 1. **ArrayBlockingQueue**:有界阻塞佇列; 2. **LinkedBlockingQueue**:連結串列結構(大小預設值為Integer.MAX_VALUE)的阻塞佇列; 3. PriorityBlockingQueue:支援優先順序排序的無界阻塞佇列; 4. DelayQueue:使用優先順序佇列實現的延遲無界阻塞佇列; 5. SynchronousQueue:不儲存元素的阻塞佇列,相當於只有一個元素; 6. LinkedTransferQueue:連結串列組成的無界阻塞佇列; 7. LinkedBlockingDeque:連結串列組成的雙向阻塞佇列。 對於 BlockingQueue 來說,核心操作主要有幾類:插入、刪除、查詢。