Java多線程編程:實現并發處理的高效利器
作者:Stevedash
發表于:2023年8月13日 20點45分
來源:Java 多線程編程 | 菜鳥教程 (runoob.com)
? 在計算機領域,多線程編程是一項重要的技術,可以使程序同時執行多個任務,充分利用計算資源,提高系統的性能和響應能力。Java作為一門廣泛應用的編程語言,提供了豐富的多線程編程支持,使得開發人員可以輕松實現并發處理。本篇博客將向您介紹Java多線程編程的基本概念、創建線程的方式、線程同步和線程通信等內容,同時還會涉及到線程的生命周期、線程的優先級和線程的常用方法。
多線程編程的優勢
? 多線程編程在提高程序性能、資源利用率和響應速度方面具有明顯的優勢。通過充分利用多核處理器,可以在同一時刻處理多個任務,提高系統的整體吞吐量。此外,多線程還可以用于實現一些需要并發執行的場景,如并發請求處理、數據采集、實時計算等。
創建線程的方式
Java多線程編程有三種常見的方式來創建線程:
-
繼承Thread類: 創建一個類繼承
Thread
類,并重寫run()
方法來定義線程要執行的任務。 -
實現Runnable接口: 創建一個實現
Runnable
接口的類,并實現run()
方法,然后通過Thread
類的構造方法創建線程對象。 -
實現Callable接口: 創建一個實現
Callable
接口的類,并實現call()
方法,可以獲取線程執行后的返回值。
? 每種方式都有其特點,選擇合適的方式取決于具體需求。使用繼承Thread類的方式編寫簡單,但由于Java不支持多重繼承,可能限制了其他類的繼承。而實現Runnable或Callable接口的方式可以更靈活地管理線程。
線程的生命周期
線程是一個動態執行的過程,它也有一個從產生到死亡的過程。
下圖顯示了一個線程完整的生命周期。
線程的生命周期可以分為以下幾個階段:
- 新建狀態(New): 使用 new 關鍵字和 Thread 類或其子類建立一個線程對象后,該線程對象就處于新建狀態。它保持這個狀態直到程序 start() 這個線程。
- 就緒狀態(Runnable): 當調用線程的
start()
方法后,線程進入就緒狀態,就緒狀態的線程處于就緒隊列中,要等待JVM里線程調度器的調度,等待獲取CPU時間片執行。 - 運行狀態(Running): 如果就緒狀態的線程獲取 CPU 資源,就可以執行 run(),此時線程便處于運行狀態。處于運行狀態的線程最為復雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。
- 阻塞狀態(Blocked): **如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線程就從運行狀態進入阻塞狀態。**在睡眠時間已到或獲得設備資源后可以重新進入就緒狀態。可以分為三種:
- 等待阻塞:運行狀態中的線程執行 wait() 方法,使線程進入到等待阻塞狀態。
- 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因為同步鎖被其他線程占用)。
- 其他阻塞:通過調用線程的 sleep() 或 join() 發出了 I/O 請求時,線程就會進入到阻塞狀態。當sleep() 狀態超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程重新轉入就緒狀態。
- 死亡狀態:(Terminated): 一個運行狀態的線程完成任務或者其他終止條件發生時,又或者當線程的
run()
方法執行完畢,線程進入終止狀態。
線程的優先級
每個線程都有一個優先級,用于指示線程在競爭CPU時間片時的優先順序。線程的優先級分為1到10,其中1為最低優先級,10為最高優先級。可以使用setPriority()
方法設置線程的優先級,但并不能保證優先級高的線程一定會先執行。**默認情況下,每一個線程都會分配一個優先級 NORM_PRIORITY(5)。**具有較高優先級的線程對程序更重要,并且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴于平臺。
線程的常用方法
Java提供了一些常用的線程方法,用于管理線程的執行和狀態:
-
start():
啟動線程,使其進入就緒狀態。 -
join():
等待線程執行完畢。 -
sleep():
使線程休眠一段時間。 -
yield():
讓出CPU時間片,讓其他線程執行。 -
isAlive():
判斷線程是否還存活。
示例代碼
以下是一個簡單的Java程序,演示了如何創建并啟動多個線程,并使用線程的優先級和常用方法:
public class MultiThreadDemo {public static void main(String[] args) {Thread thread1 = new Thread(new MyRunnable("Thread 1"));Thread thread2 = new Thread(new MyRunnable("Thread 2"));thread1.setPriority(Thread.MAX_PRIORITY);thread2.setPriority(Thread.MIN_PRIORITY);thread1.start();thread2.start();try {thread1.join(); // 等待thread1執行完畢thread2.join(); // 等待thread2執行完畢} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主線程結束");}//實現Runnable接口創建線程static class MyRunnable implements Runnable {private String name;public MyRunnable(String name) {this.name = name;}//通過Callable接口和Future創建線程static class MyCallable implements Callable<Integer> {private String name;public MyCallable(String name) {this.name = name;}@Overridepublic Integer call() throws Exception{//...return Integer;}//繼承Thread類創建線程 三選一static class MyThread extends Thread{private String name;public MyCallable(String name) {this.name = name;}}//必須要重寫run方法@Overridepublic void run() {for (int i = 1; i <= 5; i++) {System.out.println(name + ": " + i);try {Thread.sleep(1000); // 休眠1秒} catch (InterruptedException e) {e.printStackTrace();}}}}
}
下面是Thread 方法
下表列出了Thread類的一些重要方法:
序號 | 方法描述 |
---|---|
1 | public void start() 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。 |
2 | public void run() 如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作并返回。 |
3 | public final void setName(String name) 改變線程名稱,使之與參數 name 相同。 |
4 | public final void setPriority(int priority) 更改線程的優先級。 |
5 | public final void setDaemon(boolean on) 將該線程標記為守護線程或用戶線程。 |
6 | public final void join(long millisec) 等待該線程終止的時間最長為 millis 毫秒。 |
7 | public void interrupt() 中斷線程。 |
8 | public final boolean isAlive() 測試線程是否處于活動狀態。 |
上述方法是被 Thread 對象調用的,下面表格的方法是 Thread 類的靜態方法。
序號 | 方法描述 |
---|---|
1 | public static void yield() 暫停當前正在執行的線程對象,并執行其他線程。 |
2 | public static void sleep(long millisec) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。 |
3 | public static boolean holdsLock(Object x) 當且僅當當前線程在指定的對象上保持監視器鎖時,才返回 true。 |
4 | public static Thread currentThread() 返回對當前正在執行的線程對象的引用。 |
5 | public static void dumpStack() 將當前線程的堆棧跟蹤打印至標準錯誤流。 |
重點了解一下SetDeamon()標記成守護線程或者用戶線程
基本概念:
? 守護線程(Daemon Thread)是一種在后臺運行的線程,它的存在不會阻止程序的終止。與之相對,用戶線程(User Thread)是主要用于執行應用程序邏輯的線程,“如果所有的用戶線程都執行完畢,JVM 就會退出,而不管守護線程是否還在運行。”
? 在 Java 中,我們可以通過 setDaemon(true)
方法將一個線程設置為守護線程。**默認情況下,線程都是用戶線程,不會影響程序的終止。**如果想要使用守護線程,可以在創建線程后調用 setDaemon(true)
來設置它為守護線程。
守護線程的主要作用有以下幾點:
- 后臺任務執行: 守護線程通常用于執行一些不需要持續運行的后臺任務,如垃圾回收(Garbage Collection)、內存管理等。通過將這些任務放在守護線程中,可以在主要任務執行完畢后,自動進行清理等工作,提高系統的整體性能和資源利用率。
- 資源管理: 守護線程可以用于監控和管理一些資源,如數據庫連接池、網絡連接等。當用戶線程都結束時,守護線程可以負責關閉這些資源,防止資源泄露和浪費。
- 周期性任務: 守護線程還可以用于執行周期性的任務,如定時器任務。這些任務可以在“后臺周期性地執行,而不影響主要業務邏輯的進行。”
? PS(這很重要基本上就是守護線程的重點):守護線程在程序終止時會突然中斷,因此不應該在它們的代碼中進行需要確保完整性的操作。另外,守護線程不應該依賴于特定的執行順序,因為它們的執行可能會受到系統資源調度的影響。
三種創建線程的方式對比
-
繼承
Thread
類:- 創建線程的方式之一是繼承
Thread
類,并重寫run
方法來定義線程的任務邏輯。 - 優點:簡單,不需要額外的類來實現接口。
- 缺點:由于 Java 不支持多繼承,因此如果已經繼承了其他類,則無法再使用這種方式創建線程。
- 示例代碼:
class MyThread extends Thread {public void run() {// 線程執行的任務邏輯} }public class ThreadExample {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();} }
- 創建線程的方式之一是繼承
-
實現
Runnable
接口:- 另一種創建線程的方式是實現
Runnable
接口,并將實現類的實例傳遞給Thread
類的構造函數。 - 優點:可以避免繼承單一父類的限制,同時更靈活,可以實現多個接口。
- 缺點:相對于繼承
Thread
類,需要額外的類來實現接口。 - 示例代碼:
class MyRunnable implements Runnable {public void run() {// 線程執行的任務邏輯} }public class RunnableExample {public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();} }
- 另一種創建線程的方式是實現
-
使用
Callable
和Future
:- 使用
Callable
接口可以創建一個帶有返回結果的任務。Future
接口可以用于獲取任務的執行結果。 - 優點:可以獲取任務的返回結果,能夠捕獲任務拋出的異常。
- 示例代碼:
import java.util.concurrent.*;class MyCallable implements Callable<Integer> {public Integer call() throws Exception {// 線程執行的任務邏輯return 42;} }public class CallableExample {public static void main(String[] args) {ExecutorService executor = Executors.newSingleThreadExecutor();Future<Integer> future = executor.submit(new MyCallable());try {int result = future.get();System.out.println("任務執行結果:" + result);} catch (Exception e) {e.printStackTrace();} finally {executor.shutdown();}} }
- 使用
三種創建線程方式對比小結:
- 采用實現 Runnable、Callable 接口的方式創建多線程時,線程類只是實現了 Runnable 接口或 Callable 接口,還可以繼承其他類。
- 使用繼承 Thread 類的方式創建多線程時,編寫簡單,如果需要訪問當前線程,則無需使用 Thread.currentThread() 方法,直接使用 this 即可獲得當前線程。
- 繼承
Thread
類和實現Runnable
接口是最基本的方式,而使用Callable
和Future
可以獲得更多的控制和返回結果的能力。
線程的幾個主要概念
在多線程編程時,你需要了解以下幾個概念:
- 線程同步
- 線程間通信
- 線程死鎖
- 線程控制:掛起、停止和恢復
注意事項和進階功能
-
多線程編程需要注意線程安全性和資源競爭問題,合理使用同步機制來保證數據的一致性。
-
Java提供了線程池、并發集合等工具類來簡化多線程編程,提高效率和性能。
-
在處理復雜場景時,可以使用
Executor
框架、Future
接口等實現更高級的線程管理和任務調度。
總結
? Java多線程編程是實現并發處理的有效手段,可以提高程序性能和響應能力。通過學習線程創建方式、線程同步、線程通信、線程的生命周期、線程的優先級和線程的常用方法,我們可以在應用程序中合理使用多線程來實現并發任務。
作者:Stevedash
發表于:2023年8月13日 20點45分
來源:Java 多線程編程 | 菜鳥教程 (runoob.com)
注:本文內容基于個人學習理解,如有錯誤或疏漏,歡迎指正。感謝閱讀!如果覺得有幫助,請點贊和分享。