Java 并發編程系列(上篇):多線程深入解析

一、開篇:走進 Java 并發編程世界

????????在現代軟件開發中,充分利用多核 CPU 的計算能力至關重要,Java 并發編程為我們提供了實現這一目標的工具。從簡單的多線程任務并行執行,到復雜的高并發系統設計,掌握并發編程是進階 Java 工程師的關鍵一步。本篇作為上篇,聚焦多線程基礎、線程狀態、線程組與優先級、進程線程區別,以及synchronized鎖的基礎與狀態體系 。

? ? ? ? 先疊個甲,由于這一塊內容是面試必問的部分,也是經常用的,內容太多,我分三篇逐步更新,從基礎線程概念到線程池、鎖等復雜場景。

二、Java 多線程入門:創建與核心邏輯

(一)創建線程的三種方式

1. 繼承 Thread 類:線程邏輯內聚

????????繼承Thread,重寫run方法定義線程執行體。調用start方法啟動新線程(直接調用run是普通方法調用,不會新建線程 )。

class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + "執行,i=" + i);}}
}public class ThreadInheritDemo {public static void main(String[] args) {MyThread thread1 = new MyThread();thread1.setName("自定義線程1");thread1.start(); MyThread thread2 = new MyThread();thread2.setName("自定義線程2");thread2.start(); }
}

運行后,兩個線程交替輸出,體現多線程并發執行特性,適合線程邏輯簡單且無需復用的場景。

2. 實現 Runnable 接口:解耦任務與線程

????????將線程執行邏輯封裝到Runnable實現類,避免單繼承限制(Java 類僅能單繼承,但可實現多個接口 ),方便任務邏輯復用。????????

class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + "執行,i=" + i);}}
}public class RunnableImplDemo {public static void main(String[] args) {MyRunnable runnable = new MyRunnable();Thread threadA = new Thread(runnable, "線程A");Thread threadB = new Thread(runnable, "線程B");threadA.start();threadB.start();}
}

?Runnable作為任務載體,被不同線程實例執行,常用于線程池、任務分發等場景。

3. 實現 Callable 接口:支持返回結果

??CallableRunnable類似,但call方法有返回值,需結合FutureFutureTask獲取結果,適用于需線程執行產出數據的場景。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 5; i++) {sum += i;}return sum;}
}public class CallableImplDemo {public static void main(String[] args) {MyCallable callable = new MyCallable();FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread thread = new Thread(futureTask, "計算線程");thread.start();try {Integer result = futureTask.get(); System.out.println("線程計算結果:" + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}
}

futureTask.get()會阻塞直到線程執行完畢返回結果,也可設置超時時間避免永久阻塞。

二)線程關鍵疑問:run 與 start、重寫 run 的本質

1. 為何重寫 run 方法?

??Thread類默認run方法體為空(public void run() {}?),重寫run是為了定義線程實際執行的業務邏輯,讓線程啟動后執行我們期望的代碼。

2. run 方法與 start 方法的區別
  • run 方法:普通實例方法,調用時由當前線程(調用run的線程)順序執行run內代碼,不會創建新線程。
  • start 方法Thread類特殊方法,調用后觸發 JVM創建新線程,并由新線程執行run邏輯。即start是 “啟動新線程 + 執行任務”,run只是 “執行任務(當前線程)” 。
class TestThread extends Thread {@Overridepublic void run() {System.out.println("當前線程名:" + Thread.currentThread().getName());}
}public class StartRunDistinguish {public static void main(String[] args) {TestThread thread = new TestThread();thread.setName("自定義線程");thread.run(); thread.start(); }
}

輸出:
當前線程名:main
當前線程名:自定義線程
清晰展示兩者差異,start才是真正啟動新線程的方式。

三)控制線程的常用方法

1. sleep ():線程休眠

????????使當前線程暫停指定時間(毫秒),讓出 CPU 但不釋放對象鎖(若持有鎖)。常用于模擬延遲、協調執行節奏。

public class SleepUsage {public static void main(String[] args) {new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + "執行,i=" + i);try {Thread.sleep(1000); } catch (InterruptedException e) {e.printStackTrace();}}}, "睡眠線程").start();}
}

線程每隔 1 秒輸出,期間 CPU 可被其他線程使用,但鎖資源(若涉及同步代碼)不會釋放。

2. join ():線程等待

讓當前線程等待目標線程執行完畢后再繼續,用于協調線程執行順序。

public class JoinUsage {public static void main(String[] args) throws InterruptedException {Thread threadA = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("線程A執行,i=" + i);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}});threadA.start();threadA.join(); System.out.println("線程A執行完畢,main線程繼續");}
}

?“main 線程繼續” 需在線程 A 執行完 3 次循環后才輸出,確保執行順序。

其實也可以這樣理解,讓A線程插隊,當前線程main在A線程執行完畢后再執行

3. setDaemon ():守護線程

????????守護線程為用戶線程服務,所有用戶線程結束后,守護線程自動終止(如 JVM 的 GC 線程 )。需在start前設置。

public class DaemonUsage {public static void main(String[] args) {Thread daemonThread = new Thread(() -> {while (true) {System.out.println("守護線程運行中...");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}});daemonThread.setDaemon(true); daemonThread.start();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main線程結束");}
}

main 線程(用戶線程)結束后,守護線程隨之停止,不會無限循環。

4. yield ():線程讓步

????????當前線程主動讓出 CPU 使用權,回到就緒狀態重新參與調度,僅為 “建議”,不保證生效,用于給同優先級線程更多執行機會。
?

public class YieldUsage {public static void main(String[] args) {Thread thread1 = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("線程1執行,i=" + i);Thread.yield(); }});Thread thread2 = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("線程2執行,i=" + i);}});thread1.start();thread2.start();}
}

線程 1 每次循環嘗試讓步,線程 2 可能更頻繁執行,但因調度不確定性,結果不絕對一致。

三、Java 線程的 6 種狀態:生命周期全解析

Java 線程狀態定義在Thread.State枚舉中,共 6 種,理解狀態轉換對排查線程問題、優化并發邏輯至關重要。

(一)狀態枚舉與含義

  1. NEW(新建):線程對象已創建(如new Thread()?),但未調用start,未啟動。
  2. RUNNABLE(可運行):線程已啟動(start調用后),可能正在 CPU 執行,或在就緒隊列等待調度,也就是就緒狀態。
  3. BLOCKED(阻塞):線程競爭synchronized鎖失敗,進入阻塞態,等待鎖釋放。
  4. WAITING(等待):線程調用Object.wait()(無超時)、Thread.join()(無超時)、LockSupport.park()等,無時限等待喚醒。
  5. TIMED_WAITING(計時等待):調用Thread.sleep(long)Object.wait(long)Thread.join(long)?、LockSupport.parkNanos/parkUntil等,限時等待,超時自動喚醒。
  6. TERMINATED(終止):線程執行完畢(run正常結束或拋未捕獲異常),生命周期結束。

(二)狀態轉換示例

通過代碼觀察線程從新建到終止的狀態變化:

public class ThreadStateAnalysis {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {try {Thread.sleep(1500); synchronized (ThreadStateAnalysis.class) {System.out.println("線程執行中,獲取鎖");}} catch (InterruptedException e) {e.printStackTrace();}});System.out.println("線程狀態(NEW):" + thread.getState()); thread.start();Thread.sleep(500); System.out.println("線程狀態(TIMED_WAITING):" + thread.getState()); Thread.sleep(1500); System.out.println("線程狀態(RUNNABLE/執行中):" + thread.getState()); thread.join(); System.out.println("線程狀態(TERMINATED):" + thread.getState()); }
}

?????????結合getState()與線程執行邏輯,可清晰看到狀態NEWTIMED_WAITINGRUNNABLETERMINATED的流轉。實際調試中,可借助 JConsole、VisualVM 等工具直觀分析復雜狀態切換。

四、線程組與線程優先級:管理與調度輔助

(一)線程組(ThreadGroup)

????????線程組用于批量管理線程,可統一設置優先級、捕獲未處理異常等。默認情況下,新建線程加入創建它的線程所在組(通常是main線程組 )。

public class ThreadGroupManagement {public static void main(String[] args) {ThreadGroup customGroup = new ThreadGroup("自定義線程組");Thread thread1 = new Thread(customGroup, () -> {System.out.println("線程1所屬組:" + Thread.currentThread().getThreadGroup().getName());}, "線程1");Thread thread2 = new Thread(customGroup, () -> {System.out.println("線程2所屬組:" + Thread.currentThread().getThreadGroup().getName());}, "線程2");thread1.start();thread2.start();Thread[] threads = new Thread[customGroup.activeCount()];customGroup.enumerate(threads);for (Thread t : threads) {System.out.println("線程組線程:" + t.getName());}}
}

線程組輔助批量操作,但現代并發更依賴線程池,線程組使用場景逐漸減少,了解即可。

(二)線程優先級:調度 “建議”

????????線程優先級是調度器優先調度的 “建議”,范圍 1(最低)~10(最高),默認 5。優先級高的線程理論上獲取 CPU 時間片機會更多,但不保證執行順序(受操作系統調度策略影響 )。

public class ThreadPriorityControl {public static void main(String[] args) {Thread highPriority = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("高優先級線程執行,i=" + i);}});highPriority.setPriority(Thread.MAX_PRIORITY); Thread lowPriority = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("低優先級線程執行,i=" + i);}});lowPriority.setPriority(Thread.MIN_PRIORITY); highPriority.start();lowPriority.start();}
}

????????多次運行可能觀察到高優先級線程更 “活躍”,但因系統調度不確定性,不能完全依賴優先級控制執行順序,實際開發需謹慎使用。

五、進程與線程的區別:資源與執行單元

(一)核心差異對比

對比維度
進程
線程
資源分配單位操作系統分配資源(內存、文件句柄等)的基本單位進程內的執行單元,CPU 調度的基本單位
資源占用獨立地址空間,資源消耗大共享進程資源(內存、文件描述符等),消耗小
上下文切換成本高(需切換地址空間、寄存器等)
低(主要切換寄存器、程序計數器)
通信復雜度進程間通信復雜(IPC:管道、socket 等)線程間通信簡單(共享內存)
獨立性進程間相互獨立,一個崩潰不影響其他進程線程同屬進程,一個崩潰可能致進程崩潰

(二)通俗類比

以 “在線文檔編輯應用” 為例:

  • 進程:整個應用是進程,操作系統為其分配獨立內存,存儲代碼、用戶數據等,是資源隔離的單位。
  • 線程:拼寫檢查、自動保存、實時協作同步等功能,作為線程共享進程內存,協作完成任務。若拼寫檢查線程崩潰,可能導致整個應用(進程)異常,體現線程對進程的依賴。

?

六、synchronized 關鍵字:鎖的基礎與狀態體系

(一)鎖的基本認知:基于對象的鎖

????????Java 中每個對象均可作為鎖,常說的 “類鎖” 本質是Class對象的鎖(Class對象在 JVM 加載類時創建,唯一對應類元數據 )。通過synchronized實現同步,保障多線程下共享資源的原子性、可見性。

(二)synchronized 的三種使用形式

1. 同步實例方法(鎖當前對象)
????????銀行賬戶(Account)有一個withdraw方法,一個人在不同設備上同時取錢,多線程可能同時取款,需保證余額正確。

代碼示例

public class Account {private double balance;public Account(double balance) {this.balance = balance;}// 同步實例方法:鎖當前對象(this)public synchronized void withdraw(double amount) {if (balance >= amount) {try {Thread.sleep(100); // 模擬業務耗時} catch (InterruptedException e) {e.printStackTrace();}balance -= amount;System.out.println(Thread.currentThread().getName() + "取款成功,余額:" + balance);} else {System.out.println(Thread.currentThread().getName() + "取款失敗,余額不足");}}
}
  • 鎖的是當前對象(this),若多個線程操作同一對象,會互斥。
  • 若多個線程操作不同對象,則互不影響(每個對象有獨立的鎖)。
2. 同步靜態方法(鎖類對象)
統計網站訪問量(靜態變量visitCount),多線程并發訪問需保證計數正確。

代碼示例

public class WebSite {private static int visitCount = 0;// 同步靜態方法:鎖類對象(WebSite.class)public static synchronized void incrementVisit() {visitCount++;System.out.println(Thread.currentThread().getName() + "訪問,總訪問量:" + visitCount);}
}
  • 鎖的是類的Class對象(全局唯一),無論創建多少實例,所有線程都會互斥。
  • 適合保護靜態共享資源(如全局計數器、配置信息)。
3. 同步代碼塊

電商系統中,商品庫存(stock)和訂單號生成器(orderIdGenerator)需分別加鎖。

代碼示例:

public class ShoppingSystem {private int stock = 10;private static final Object STOCK_LOCK = new Object(); // 庫存鎖private static final Object ORDER_LOCK = new Object(); // 訂單號鎖private static int orderId = 0;// 扣減庫存public void reduceStock() {synchronized (STOCK_LOCK) { // 鎖庫存專用對象if (stock > 0) {stock--;System.out.println(Thread.currentThread().getName() + "扣減庫存成功,剩余:" + stock);} else {System.out.println(Thread.currentThread().getName() + "庫存不足");}}}// 生成訂單號public static void generateOrderId() {synchronized (ORDER_LOCK) { // 鎖訂單號專用對象orderId++;System.out.println(Thread.currentThread().getName() + "生成訂單號:" + orderId);}}
}
  • 鎖對象可以是任意Object,推薦使用private static final修飾,避免外部訪問。
  • 縮小鎖的范圍,提高并發性能(如只鎖需要保護的代碼,而非整個方法)。

結合場景更容易理解,注意理解,而非死記硬背?

(三)synchronized 的四種鎖狀態

????????JVM 對synchronized鎖進行優化,存在四種狀態:偏向鎖、輕量級鎖、重量級鎖、無鎖,狀態隨競爭情況升級(不能降級,單向升級 )。

后面的內容我們在下一篇中講....

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86138.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86138.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86138.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[逆向工程] C實現過程調試與鉤子安裝(二十七)

[逆向工程] C實現過程調試與鉤子安裝&#xff08;二十七&#xff09; 引言 在現代逆向工程和調試領域&#xff0c;能夠動態監控和操控進程執行非常關鍵。本篇文章將全面講解如何使用 C 編寫一個進程調試器——hookdbg64.exe&#xff0c;實現對目標進程的附加、監控 WriteFile…

分頁查詢的實現

第一步&#xff1a;導入pom依賴 <!--配置PageHelper分頁插件--><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version><exclusions>…

JDK17 Http Request 異步處理 源碼刨析

為什么可以異步&#xff1f; #調用起始源碼 // 3. 發送異步請求并處理響應 CompletableFuture future client.sendAsync( request, HttpResponse.BodyHandlers.ofString() // 響應體轉為字符串 ).thenApply(response -> { // 狀態碼檢查&#xff08;非200系列拋出異常&…

會計 - 合并4 - 或有對價的會計處理

一、多次交易(構成一攬子交易)形成非同一控制下企業合并 構成一攬子交易的,在取得控制權時確認長期股權投資;取得控制權之前已支付的款項應作為預付投資款項(通常以”預付賬款“科目核算)處理。 滿足以下一種或多種情況的,通常應將多次交易事項作為“一攬子交易”進行會…

【HTTP三個基礎問題】

面試官您好&#xff01;HTTP是超文本傳輸協議&#xff0c;是互聯網上客戶端和服務器之間傳輸超文本數據&#xff08;比如文字、圖片、音頻、視頻等&#xff09;的核心協議&#xff0c;當前互聯網應用最廣泛的版本是HTTP1.1&#xff0c;它基于經典的C/S模型&#xff0c;也就是客…

NLP中的input_ids是什么?

在自然語言處理(NLP)中,input_ids 是什么 在自然語言處理(NLP)中,input_ids 是將文本轉換為模型可處理的數字表示后的結果,是模型輸入的核心參數之一。 一、基本概念 文本數字化 原始文本(如 “Hello world!”)無法直接被模型處理,需要通過分詞器(Tokenizer) 將其…

?? Linux Docker 基本命令參數詳解

&#x1f433; Linux Docker 基本命令參數詳解 &#x1f4d8; 1. Docker 簡介 Docker 是一個開源的容器化平臺&#xff0c;它通過將應用及其依賴打包到一個輕量級、可移植的容器中&#xff0c;從而實現跨平臺運行。Docker 采用 C/S 架構&#xff0c;服務端稱為 Docker Daemon&a…

Spring IoC 模塊設計文檔

注&#xff1a;碼友們&#xff0c;我們是從設計的角度一步步學習和分解Spring&#xff1b;所以不要一上來就想看源碼&#xff0c;也不需要關心Spring具體加載進去的&#xff1b;我們只封裝工具&#xff08;如IoC&#xff09;&#xff0c;至于調用&#xff0c;暫時不用考慮&…

Linux(生產消費者模型/線程池)

目錄 一 生產消費者模型 1. 概念&#xff1a; 2. 基于阻塞隊列的生產消費者模型&#xff1a; 1. 對鎖封裝 2. 對條件變量封裝 二 信號量(posix) 1. 概念 2. API 3. 基于環形隊列的生產消費者模型 三 線程池 1. 概念 2. 示例 四 補充字段 1. 可重入函數 VS 線程安…

無線網絡掃描與分析工具 LizardSystems Wi-Fi Scanner 25.05

—————【下 載 地 址】——————— 【?本章下載一】&#xff1a;https://pan.xunlei.com/s/VOS4QQ9APt3FgFQcxyArBiZlA1?pwdi4du# 【?本章下載二】&#xff1a;https://pan.xunlei.com/s/VOS4QQ9APt3FgFQcxyArBiZlA1?pwdi4du# 【百款黑科技】&#xff1a;https://uc…

Java Map完全指南:從基礎到高級應用

文章目錄 1. Map接口概述Map的基本特性 2. Map接口的核心方法基本操作方法批量操作方法 3. 主要實現類詳解3.1 HashMap3.2 LinkedHashMap3.3 TreeMap3.4 ConcurrentHashMap 4. 高級特性和方法4.1 JDK 1.8新增方法4.2 Stream API結合使用 5. 性能比較和選擇建議性能對比表選擇建…

[最全總結]城市災害應急管理系統

城市災害應急管理集成系統 | 國家重點研發政府間合作項目 Vue+ElementUI+Bpmn+Cesium+Java SpringBoot 項目描述 在智慧城市戰略背景下,項目面向內澇、團霧和火災等災害,開發了集災害模型集成模擬、場景可視化與應急預案管理于一體的系統,系統各子模塊進行軟件功能測試,測…

QtWidgets模塊功能及架構解析

QtWidgets 是 Qt 框架中用于創建傳統桌面應用程序圖形用戶界面(GUI)的核心模塊。在 Qt 6.0 中&#xff0c;QtWidgets 模塊繼續提供豐富的 UI 組件和功能&#xff0c;盡管 Qt 正在向 QML 方向演進&#xff0c;但 QtWidgets 仍然是許多桌面應用程序的基礎。 一、主要功能 基礎窗…

grep、wc 與管道符快速上手指南

&#x1f3af; Linux grep、wc 與管道符快速上手指南&#xff1a;從入門到實用 &#x1f4c5; 更新時間&#xff1a;2025年6月7日 &#x1f3f7;? 標簽&#xff1a;Linux | grep | wc | 管道符 | 命令行 文章目錄 前言&#x1f31f; 一、grep、wc 和管道符簡介1.核心功能2.核心…

C++11 右值引用:從入門到精通

文章目錄 一、引言二、左值和右值&#xff08;一&#xff09;概念&#xff08;二&#xff09;區別和判斷方法 三、左值引用和右值引用&#xff08;一&#xff09;左值引用&#xff08;二&#xff09;右值引用 四、移動語義&#xff08;一&#xff09;概念和必要性&#xff08;二…

java復習 04

心情復雜呢&#xff0c;現在是6.7高考第一天&#xff0c;那年今日此時此刻我還在考場掙扎數學&#xff0c;雖然結果的確很糟糕&#xff0c;&#xff0c;現在我有點對自己生氣明明很多事情待辦確無所事事沒有目標&#xff0c;不要忘記曾經的自己是什么樣子的&#xff0c;去年今日…

從零開始搭建 Pytest 測試框架(Python 3.8 + PyCharm 版)

概述 在軟件開發中&#xff0c;自動化測試是確保代碼質量的重要方式。而 Pytest 是一個功能強大且易于上手的 Python 測試框架&#xff0c;非常適合初學者入門。 本文將帶你一步步完成&#xff1a; 安裝和配置 Pytest在 PyCharm 中搭建一個清晰的測試項目結構 準備工作 在…

用電腦通過網口控制keysight示波器

KEYSIGHT示波器HD304MSO性能 亮點: 體驗 200 MHz 至 1 GHz 的帶寬和 4 個模擬通道。與 12 位 ADC 相比,使用 14 位模數轉換器 (ADC) 將垂直分辨率提高四倍。使用 10.1 英寸電容式觸摸屏輕松查看和分析您的信號。捕獲 50 μVRMS 本底噪聲的較小信號。使用獨有區域觸摸在幾秒…

Java Smart 系統題庫試卷管理模塊設計:從需求到開發的實戰指南

在教育信息化不斷推進的背景下&#xff0c;高效的題庫及試卷管理系統至關重要。Java Smart 系統中的題庫及試卷管理模塊&#xff0c;旨在為教師提供便捷的試題錄入、試卷生成與管理功能&#xff0c;同時方便學生在線練習與考試。本文將詳細介紹該模塊的設計思路與核心代碼實現。…

PDF圖片和表格等信息提取開源項目

文章目錄 綜合性工具專門的表格提取工具經典工具 綜合性工具 PDF-Extract-Kit - opendatalab開發的綜合工具包&#xff0c;包含布局檢測、公式檢測、公式識別和OCR功能 倉庫&#xff1a;opendatalab/PDF-Extract-Kit特點&#xff1a;功能全面&#xff0c;包含表格內容提取的S…