本章將會介紹 Java 多執行緒併發程式設計的入門知識,從 Java 多執行緒常用實現開始,由淺入深瞭解 Java 兩種常用的執行緒池建立使用及其適用場景。透過對 java.util.concurrent.ThreadPoolExecutor 原始碼的解析,瞭解自定義 Java 執行緒池的幾個重要引數,並掌握執行緒池內在的執行邏輯,達到自定義 Java 執行緒池的目的。
併發與並行
在進行 Java 多執行緒程式設計之前,首先分享一個這個概念:併發和並行。
這兩個概念在實際工作很少去刻意區分,屬於非常基礎的知識。如果你想了解 Java 多執行緒程式設計,就必需先搞清楚這兩個概念以及差異。
併發 (Concurrency) 和並行 (Parallelism) 都是指同時處理多個事務的能力,但是這兩個概念本質上還是有差異的。總結來說並行指的是時間上的同時發生,而併發並不一定是。如果站在觀察者角度來看,併發看起來很像並行。
要想更好地理解,小故事是無法避免的,下面請讓聽一聽我這個 “超市結賬” 版本。
這裡有一家叫 “小八” 的超市,裡面只有一個收銀臺,但是確有兩個收銀通道。平時空閒的時候只開放一條收銀通道,人多的時候開發兩條收銀通道,讓所有顧客更快完成結賬付款,減少等待時間。
但實際情況是這樣的,只有一位收銀員,但是收銀臺對於顧客是黑盒,顧客完全無法瞭解收銀臺裡面如何執行,更無法知道真相:只有一位收銀員。
對於單個顧客,他們結賬流程是:1. 把商品挨個掃描計價;2. 計算顧客應付金額;3. 顧客付款;4. 顧客收拾商品結束購物。
當空閒的時候,每個顧客結賬過程大約需要 1 分鐘。當繁忙的時候,開放兩條通道,平均每個顧客結賬也需要 1 分鐘。乍一看,收銀臺的效能提升了 1 倍。
實際情況是這樣的:這位收銀員,一邊等待第一條通道顧客出示付款碼,一邊給第二條通道的顧客掃描計價;一邊等待第一條通道的顧客自己打包商品,一邊再給第二條通道的顧客找零錢。如圖 1-1 所示:
這個小故事裡面,超市相當於我們的計算機,收銀臺或者收銀員相當於 CPU。當我們只有一顆 CPU 時,依然可以同時處理兩條結賬通道的顧客。這裡的通道相當於執行緒。原來 1 分鐘只能完成一個結賬週期,透過增加結賬通道提升了 1 倍的效能。
如果你站在顧客的視角,兩條通道顧客在同時結賬,這個就叫做併發。如果超市老闆又招聘了一位收銀員,兩位收銀員分別處理兩條結賬通道,兩條通道相互不影響,這個就叫做並行。如圖 1-2 所示:
對於 CPU 來說,程式就相當於是超市結賬的顧客,所以在使用 Java 進行效能測試中,我們關心更多的就是併發。
併發和並行是電腦科學中兩個密切相關但概念上不同的術語,主要用於描述任務的執行方式。
特徵總結:
特性 | 併發(Concurrency) | 並行(Parallelism) |
---|---|---|
定義 | 多個任務交替執行,體現為任務之間的協作與排程,側重任務切換。 | 多個任務同時執行,強調同時性,利用多核或多處理器資源。 |
執行單位 | 任務可以在單核或多核上透過時間片輪轉執行(分時共享)。 | 任務必須在多核、多執行緒或多處理器上同時執行。 |
特點 | - 更注重任務的邏輯結構(任務可以部分完成)。 - 強調程式的設計能力,避免競態條件。 |
- 強調硬體能力,要求硬體支援同時執行。 - 提升任務吞吐量。 |
典型場景 | - 多工處理:如在 GUI 中,UI 響應使用者互動的同時處理後臺資料更新。 - 非同步 I/O 操作。 |
- 科學計算:大規模矩陣計算、影像處理等。 - 並行資料處理,如 MapReduce、GPU 運算。 |
硬體要求 | 不需要依賴多核,多執行緒環境即可實現。 | 需要依賴多核、多處理器或 GPU。 |
技術示例 | - Java 的執行緒池(如 Executor)。 - Golang 的 Goroutines(協程)。 |
- CUDA 的 GPU 程式設計。 - OpenMP 多執行緒計算。 |
關鍵問題 | 如何設計任務的交替邏輯,避免死鎖、資源競爭。 | 如何分配任務到多個計算單元,最大化硬體利用率。 |
書的名字:從 Java 開始做效能測試 。
如果本書內容對你有所幫助,希望各位多多讚賞,讓我可以貼補家用。讚賞兩位數可以提前閱讀未公開章節。我也會嘗試製作本書的影片教程,包括必要的答疑。
FunTester 原創精華
- 混沌工程、故障測試、Web 前端
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go
- 白盒、工具、爬蟲、UI 自動化
- 理論、感悟、影片