Java並髮指南1:並發基礎與Java多執行緒
- 2019 年 11 月 20 日
- 筆記
本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫里查看
本文是微信公眾號【Java技術江湖】的《Java並髮指南》其中一篇,本文大部分內容來源於網路,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格內容,引用其中了一些比較好的部落格文章,如有侵權,請聯繫作者。
該系列博文會告訴你如何全面深入地學習Java並發技術,從Java多執行緒基礎,再到並發編程的基礎知識,從Java並發包的入門和實戰,再到JUC的源碼剖析,一步步地學習Java並發編程,並上手進行實戰,以便讓你更完整地了解整個Java並發編程知識體系,形成自己的知識框架。
為了更好地總結和檢驗你的學習成果,本系列文章也會提供一些對應的面試題以及參考答案。
如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公眾號【Java技術江湖】聯繫作者,歡迎你參與本系列博文的創作和修訂。
1多執行緒的優點
- 資源利用率更好
- 程式設計在某些情況下更簡單
- 程式響應更快
1.1資源利用率更好案例
方式1 從磁碟讀取一個文件需要5秒,處理一個文件需要2秒。處理兩個文件則需要14秒
1 5秒讀取文件A2 2秒處理文件A3 5秒讀取文件B4 2秒處理文件B5 ---------------------6 總共需要14秒
方式2 從磁碟中讀取文件的時候,大部分的CPU非常的空閑。它可以做一些別的事情。通過改變操作的順序,就能夠更好的使用CPU資源。看下面的順序:
1 5秒讀取文件A2 5秒讀取文件B + 2秒處理文件A3 2秒處理文件B4 ---------------------5 總共需要12秒
總結:多執行緒並發效率提高2秒
1.2程式響應更快
設想一個伺服器應用,它在某一個埠監聽進來的請求。當一個請求到來時,它把請求傳遞給工作者執行緒(worker thread),然後立刻返回去監聽。而工作者執行緒則能夠處理這個請求並發送一個回復給客戶端。
while(server is active){ listenThread for request hand request to workerThread }
這種方式,服務端執行緒迅速地返回去監聽。因此,更多的客戶端能夠發送請求給服務端。這個服務也變得響應更快。
2多執行緒的代價
2.1設計更複雜
多執行緒一般都複雜。在多執行緒訪問共享數據的時候,這部分程式碼需要特別的注意。執行緒之間的交互往往非常複雜。不正確的執行緒同步產生的錯誤非常難以被發現,並且重現以修復。
2.2上下文切換的開銷
上下文切換當CPU從執行一個執行緒切換到執行另外一個執行緒的時候,它需要先存儲當前執行緒的本地的數據,程式指針等,然後載入另一個執行緒的本地數據,程式指針等,最後才開始執行。
CPU會在一個上下文中執行一個執行緒,然後切換到另外一個上下文中執行另外一個執行緒。
上下文切換並不廉價。如果沒有必要,應該減少上下文切換的發生。
2.3增加資源消耗
每個執行緒需要消耗的資源:
CPU,記憶體(維持它本地的堆棧),作業系統資源(管理執行緒)
3競態條件與臨界區
當多個執行緒競爭同一資源時,如果對資源的訪問順序敏感,就稱存在競態條件。導致競態條件發生的程式碼區稱作臨界區。
多執行緒同時執行下面的程式碼可能會出錯:
public class Counter { protected long count = 0; public void add(long value) { this.count = this.count + value; } }
想像下執行緒A和B同時執行同一個Counter對象的add()方法,我們無法知道作業系統何時會在兩個執行緒之間切換。JVM並不是將這段程式碼視為單條指令來執行的,而是按照下面的順序
從記憶體獲取 this.count 的值放到暫存器 將暫存器中的值增加value 將暫存器中的值寫回記憶體 觀察執行緒A和B交錯執行會發生什麼 this.count = 0; A: 讀取 this.count 到一個暫存器 (0) B: 讀取 this.count 到一個暫存器 (0) B: 將暫存器的值加2 B: 回寫暫存器值(2)到記憶體. this.count 現在等於 2 A: 將暫存器的值加3
由於兩個執行緒是交叉執行的,兩個執行緒從記憶體中讀出的初始值都是0。然後各自加了2和3,並分別寫回記憶體。最終的值可能並不是期望的5,而是最後寫回記憶體的那個執行緒的值,上面例子中最後寫回記憶體可能是執行緒A,也可能是執行緒B
4執行緒的運行與創建
Java 創建執行緒對象有兩種方法:
- 繼承 Thread 類創建執行緒對象
- 實現 Runnable 介面類創建執行緒對象
注意:
在java中,每次程式運行至少啟動2個執行緒。一個是main執行緒,一個是垃圾收集執行緒。因為每當使用java命令執行一個類的時候,實際上都會啟動一個jvm,每一個jvm實際上就是在作業系統中啟動了一個進程。

5執行緒的狀態和優先順序
執行緒優先順序1 到 10 ,其中 1 是最低優先順序,10 是最高優先順序。
狀態
- new(新建)
- runnnable(可運行)
- blocked(阻塞)
- waiting(等待)
- time waiting (定時等待)
- terminated(終止)
狀態轉換

執行緒狀態流程如下:
- 執行緒創建後,進入 new 狀態
- 調用 start 或者 run 方法,進入 runnable 狀態
- JVM 按照執行緒優先順序及時間分片等執行 runnable 狀態的執行緒。開始執行時,進入 running 狀態
- 如果執行緒執行 sleep、wait、join,或者進入 IO 阻塞等。進入 wait 或者 blocked 狀態
- 執行緒執行完畢後,執行緒被執行緒隊列移除。最後為 terminated 狀態
程式碼
public class MyThreadInfo extends Thread { @Override // 可以省略 public void run() { System.out.println("run"); // System.exit(1); } public static void main(String[] args) { MyThreadInfo thread = new MyThreadInfo(); thread.start(); System.out.println("執行緒唯一標識符:" + thread.getId()); System.out.println("執行緒名稱:" + thread.getName()); System.out.println("執行緒狀態:" + thread.getState()); System.out.println("執行緒優先順序:" + thread.getPriority()); } } 結果: 執行緒唯一標識符:9 執行緒名稱:Thread-0 run 執行緒狀態:RUNNABLE