微信直接轉(zhuǎn)wordpress包頭整站優(yōu)化
Java多線程-第20章
1.創(chuàng)建線程
Java是一種支持多線程編程的編程語言。多線程是指在同一程序中同時(shí)執(zhí)行多個(gè)獨(dú)立任務(wù)的能力。在Java中,線程是一種輕量級(jí)的子進(jìn)程,它是程序中的最小執(zhí)行單元。Java的多線程編程可以通過兩種方式實(shí)現(xiàn):繼承Thread類或?qū)崿F(xiàn)Runnable接口。
-
繼承Thread類:
class MyThread extends Thread {public void run() {// 線程執(zhí)行的代碼} }
創(chuàng)建并啟動(dòng)線程:
MyThread myThread = new MyThread(); myThread.start(); // 啟動(dòng)線程
-
實(shí)現(xiàn)Runnable接口:
class MyRunnable implements Runnable {public void run() {// 線程執(zhí)行的代碼} }
創(chuàng)建并啟動(dòng)線程:
MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); // 啟動(dòng)線程
所有的程序都是通過main
方法開始執(zhí)行的。當(dāng)一個(gè)Java程序啟動(dòng)時(shí),JVM(Java虛擬機(jī))會(huì)自動(dòng)創(chuàng)建一個(gè)主線程,該線程負(fù)責(zé)執(zhí)行main
方法。在多線程編程中,你可以創(chuàng)建額外的線程來執(zhí)行其他任務(wù)。
Java提供了一些關(guān)鍵字和方法來控制線程的執(zhí)行,其中一些關(guān)鍵字包括:
synchronized
:用于控制多個(gè)線程訪問共享資源時(shí)的同步問題。wait()
、notify()
、notifyAll()
:用于實(shí)現(xiàn)線程間的通信和協(xié)調(diào)。sleep(long milliseconds)
:讓線程休眠一段時(shí)間。join()
:等待一個(gè)線程終止。yield()
:讓出CPU執(zhí)行權(quán),讓其他線程執(zhí)行。
多線程編程的主要挑戰(zhàn)之一是避免競(jìng)態(tài)條件(Race Condition)和死鎖(Deadlock)。競(jìng)態(tài)條件發(fā)生在多個(gè)線程試圖同時(shí)訪問和修改共享數(shù)據(jù)時(shí),而死鎖則是線程相互等待對(duì)方釋放資源,導(dǎo)致所有線程都無法繼續(xù)執(zhí)行的情況。
線程的狀態(tài)有以下幾種:
- 新建(New): 線程已經(jīng)創(chuàng)建,但還沒有開始執(zhí)行。
- 就緒(Runnable): 線程可以開始執(zhí)行,等待CPU時(shí)間片。
- 運(yùn)行(Running): 線程正在執(zhí)行。
- 阻塞(Blocked): 線程被阻塞,等待某個(gè)事件的發(fā)生。
- 死亡(Terminated): 線程執(zhí)行完成。
請(qǐng)注意,Java的多線程編程也有一些高級(jí)的概念和工具,如線程池、Callable和Future等,用于更靈活地處理多線程任務(wù)。
實(shí)例1:讓線程循環(huán)打印1-10的數(shù)字
實(shí)例2:讓窗口中的圖標(biāo)動(dòng)起來
2.線程的生命周期
Java線程的生命周期描述了一個(gè)線程從創(chuàng)建到運(yùn)行再到結(jié)束的整個(gè)過程,它包括多個(gè)狀態(tài),每個(gè)狀態(tài)代表了線程在不同階段的狀態(tài)。Java線程的生命周期可以分為以下幾個(gè)狀態(tài):
- 新建狀態(tài)(New):
- 當(dāng)線程對(duì)象被創(chuàng)建時(shí),它處于新建狀態(tài)。
- 此時(shí),線程還沒有開始執(zhí)行。
- 就緒狀態(tài)(Runnable):
- 當(dāng)線程調(diào)用
start()
方法后,線程進(jìn)入就緒狀態(tài)。 - 此時(shí),線程已經(jīng)準(zhǔn)備好運(yùn)行,等待獲取CPU時(shí)間片。
- 當(dāng)線程調(diào)用
- 運(yùn)行狀態(tài)(Running):
- 當(dāng)就緒狀態(tài)的線程獲取到CPU時(shí)間片時(shí),線程進(jìn)入運(yùn)行狀態(tài)。
- 此時(shí),線程正在執(zhí)行其任務(wù)。
- 阻塞狀態(tài)(Blocked):
- 線程在運(yùn)行過程中,可能由于某些原因需要暫時(shí)放棄CPU時(shí)間片,進(jìn)入阻塞狀態(tài)。
- 典型的例子包括等待I/O完成、等待獲取鎖、等待通知等。
- 當(dāng)阻塞條件解除時(shí),線程會(huì)重新進(jìn)入就緒狀態(tài)。
- 等待狀態(tài)(Waiting):
- 線程在等待某個(gè)條件滿足時(shí),會(huì)進(jìn)入等待狀態(tài)。
- 調(diào)用
Object.wait()
、Thread.join()
、LockSupport.park()
等方法可以使線程進(jìn)入等待狀態(tài)。 - 等待狀態(tài)的線程需要其他線程通知或中斷才能繼續(xù)執(zhí)行。
- 超時(shí)等待狀態(tài)(Timed Waiting):
- 線程在等待一段時(shí)間后會(huì)進(jìn)入超時(shí)等待狀態(tài)。
- 調(diào)用帶有超時(shí)參數(shù)的
Object.wait()
、Thread.sleep()
、Thread.join()
等方法會(huì)導(dǎo)致線程進(jìn)入超時(shí)等待狀態(tài)。
- 終止?fàn)顟B(tài)(Terminated):
- 線程執(zhí)行完任務(wù)或者發(fā)生了未捕獲的異常時(shí),線程進(jìn)入終止?fàn)顟B(tài)。
- 一個(gè)終止?fàn)顟B(tài)的線程不能再次啟動(dòng)。
這些狀態(tài)構(gòu)成了線程的生命周期,如下圖所示:
New -> Runnable -> (Running) -> Blocked -> (Runnable) -> (Terminated)\-> Waiting -> (Runnable) -> (Terminated)\-> Timed Waiting -> (Runnable) -> (Terminated)
注意,生命周期中的括號(hào)表示這些狀態(tài)可能是短暫的,線程可能在運(yùn)行、等待、超時(shí)等待等狀態(tài)間切換。在實(shí)際的多線程應(yīng)用中,正確地管理線程生命周期是至關(guān)重要的,以避免潛在的問題,如死鎖、競(jìng)態(tài)條件等。
3.操作線程的方法
4.1線程的休眠
線程休眠是通過Thread.sleep(long milliseconds)
方法實(shí)現(xiàn)的。這個(gè)方法讓當(dāng)前正在執(zhí)行的線程在指定的時(shí)間內(nèi)進(jìn)入休眠狀態(tài)(即暫停執(zhí)行),單位是毫秒。在指定時(shí)間過去或者線程被中斷時(shí),線程將恢復(fù)執(zhí)行。
方法簽名為:
public static native void sleep(long millis) throws InterruptedException;
millis
:休眠時(shí)間,以毫秒為單位。
注意,sleep
方法可能拋出InterruptedException
異常,因?yàn)榫€程在休眠時(shí)可以被其他線程中斷。在處理中斷時(shí),可以選擇捕獲該異常并處理,或者將異常繼續(xù)傳播出去。
4.2線程的加入
在Java中,可以使用join()
方法來等待一個(gè)線程完成其執(zhí)行。join()
方法的作用是使當(dāng)前線程等待調(diào)用join()
方法的線程執(zhí)行結(jié)束,然后再繼續(xù)執(zhí)行當(dāng)前線程。
方法簽名為:
public final void join() throws InterruptedException;
或者可以使用帶有超時(shí)參數(shù)的join(long millis)
方法:
javaCopy code
public final synchronized void join(long millis) throws InterruptedException;
millis
:等待的最大時(shí)間(以毫秒為單位)。
以下是一個(gè)簡(jiǎn)單的例子,演示了線程的加入:
class MyThread extends Thread {public void run() {for (int i = 1; i <= 5; i++) {System.out.println("Task " + i + " in progress by " + Thread.currentThread().getName());try {// 模擬任務(wù)執(zhí)行時(shí)間Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class JoinExample {public static void main(String[] args) {MyThread thread1 = new MyThread();MyThread thread2 = new MyThread();// 啟動(dòng)線程1thread1.start();try {// 等待線程1執(zhí)行完成,然后再繼續(xù)執(zhí)行主線程thread1.join();} catch (InterruptedException e) {e.printStackTrace();}// 啟動(dòng)線程2thread2.start();// 主線程繼續(xù)執(zhí)行System.out.println("Main thread continues its work.");}
}
在這個(gè)例子中,主線程啟動(dòng)了thread1
,然后調(diào)用thread1.join()
等待thread1
執(zhí)行完成,接著啟動(dòng)了thread2
。由于join()
方法會(huì)使主線程等待被調(diào)用的線程執(zhí)行完成,所以在這個(gè)例子中主線程會(huì)等待thread1
執(zhí)行完成后再啟動(dòng)thread2
。
4.3線程的中斷
線程的中斷是一種線程間的協(xié)作機(jī)制,它允許一個(gè)線程通知另一個(gè)線程,以請(qǐng)求它停止正在執(zhí)行的任務(wù)。線程的中斷通過調(diào)用interrupt()
方法來觸發(fā)。
-
中斷線程:
-
使用
interrupt()
方法中斷線程。Thread myThread = new MyThread(); myThread.start(); // ... myThread.interrupt(); // 中斷線程
-
interrupt()
方法會(huì)設(shè)置線程的中斷標(biāo)志位,但并不會(huì)立即停止線程的執(zhí)行。線程需要檢查自己的中斷狀態(tài)并在適當(dāng)?shù)臅r(shí)候終止執(zhí)行。
-
-
檢查中斷狀態(tài):
-
使用
Thread.interrupted()
方法檢查當(dāng)前線程的中斷狀態(tài),并清除中斷狀態(tài)。if (Thread.interrupted()) {// 線程已被中斷,執(zhí)行相應(yīng)的處理 }
-
或者使用
isInterrupted()
方法檢查線程的中斷狀態(tài)而不清除中斷狀態(tài)。if (myThread.isInterrupted()) {// 線程已被中斷,執(zhí)行相應(yīng)的處理 }
-
-
處理中斷:
-
在線程的執(zhí)行過程中,可以通過檢查中斷狀態(tài)來決定是否停止執(zhí)行。
public void run() {while (!Thread.interrupted()) {// 執(zhí)行任務(wù)} }
-
或者在拋出
InterruptedException
異常的地方處理中斷。public void run() {try {while (true) {// 執(zhí)行任務(wù)if (Thread.interrupted()) {throw new InterruptedException();}}} catch (InterruptedException e) {// 處理中斷異常} }
-
在處理中斷時(shí),可以選擇終止線程的執(zhí)行,或者采取其他適當(dāng)?shù)拇胧?/p>
-
中斷通常用于優(yōu)雅地停止線程,而不是強(qiáng)制終止線程。這種協(xié)作的方式允許線程在中斷請(qǐng)求到來時(shí),完成正在進(jìn)行的工作,并進(jìn)行清理工作,提高程序的健壯性。
實(shí)例3:單擊按鈕停止進(jìn)度條滾動(dòng)
4.4線程的禮讓
線程的禮讓是指一個(gè)線程表明自己愿意讓出當(dāng)前的CPU執(zhí)行時(shí)間,以便讓其他線程有機(jī)會(huì)執(zhí)行。我們可以使用Thread.yield()
方法來實(shí)現(xiàn)線程的禮讓。
方法簽名為:
public static native void yield();
Thread.yield()
方法是一個(gè)靜態(tài)方法,調(diào)用它的線程會(huì)讓出一些時(shí)間片,以便其他具有相同或更高優(yōu)先級(jí)的線程有機(jī)會(huì)執(zhí)行。然而,yield()
方法并不能保證線程會(huì)讓出CPU執(zhí)行權(quán),它只是向調(diào)度器發(fā)出一個(gè)提示。
5.線程的優(yōu)先級(jí)
線程調(diào)度器使用線程的優(yōu)先級(jí)來決定哪個(gè)線程應(yīng)該優(yōu)先執(zhí)行。線程的優(yōu)先級(jí)是一個(gè)整數(shù)值,范圍從Thread.MIN_PRIORITY
(1)到Thread.MAX_PRIORITY
(10)。默認(rèn)情況下,每個(gè)線程的優(yōu)先級(jí)都是Thread.NORM_PRIORITY
(5)。
線程的優(yōu)先級(jí)可以通過setPriority(int priority)
方法進(jìn)行設(shè)置。該方法必須在啟動(dòng)線程之前調(diào)用。
以下是設(shè)置線程優(yōu)先級(jí)的例子:
class MyThread extends Thread {public void run() {for (int i = 1; i <= 5; i++) {System.out.println("Task " + i + " in progress by " + Thread.currentThread().getName());}}
}public class PriorityExample {public static void main(String[] args) {MyThread thread1 = new MyThread();MyThread thread2 = new MyThread();// 設(shè)置線程1的優(yōu)先級(jí)為最高thread1.setPriority(Thread.MAX_PRIORITY);// 啟動(dòng)線程1thread1.start();// 啟動(dòng)線程2thread2.start();}
}
在這個(gè)例子中,thread1
的優(yōu)先級(jí)被設(shè)置為最高(Thread.MAX_PRIORITY
),而thread2
使用默認(rèn)的優(yōu)先級(jí)。在運(yùn)行時(shí),具有更高優(yōu)先級(jí)的線程更有可能被調(diào)度執(zhí)行,但并不能保證絕對(duì)順序。
注意,線程優(yōu)先級(jí)的調(diào)整并不是在所有平臺(tái)上都能生效的,而且過度依賴線程優(yōu)先級(jí)可能導(dǎo)致可移植性問題。在實(shí)際應(yīng)用中,更重要的是編寫穩(wěn)健的多線程代碼,而不是過分關(guān)注線程優(yōu)先級(jí)。
實(shí)例4:觀察不同優(yōu)先級(jí)的線程執(zhí)行完畢順序
6.線程同步
線程同步是一種機(jī)制,用于防止多個(gè)線程同時(shí)訪問共享資源,從而避免數(shù)據(jù)不一致性和競(jìng)態(tài)條件。在Java中,主要的線程同步機(jī)制包括使用synchronized
關(guān)鍵字、wait()
、notify()
和notifyAll()
方法、以及Lock
和Condition
接口等。
6.1線程安全
線程安全是指多個(gè)線程訪問某個(gè)共享資源時(shí),不會(huì)出現(xiàn)不確定的結(jié)果或?qū)е虏灰恢滦缘那闆r。在多線程環(huán)境中,如果沒有適當(dāng)?shù)耐綑C(jī)制,共享的數(shù)據(jù)結(jié)構(gòu)可能會(huì)被多個(gè)線程同時(shí)修改,從而導(dǎo)致數(shù)據(jù)不一致或其他問題。確保線程安全是多線程編程中非常重要的一個(gè)方面。
以下是一些確保線程安全的常見方式:
-
使用同步方法: 在方法上使用
synchronized
關(guān)鍵字,確保一次只有一個(gè)線程可以執(zhí)行該方法。public synchronized void synchronizedMethod() {// 同步的代碼塊 }
-
使用同步代碼塊: 在代碼塊中使用
synchronized
關(guān)鍵字,確保一次只有一個(gè)線程可以執(zhí)行同步代碼塊。public void someMethod() {// 非同步代碼synchronized (lockObject) {// 同步的代碼塊}// 非同步代碼 }
-
使用
java.util.concurrent
包中的線程安全類: Java提供了一些線程安全的數(shù)據(jù)結(jié)構(gòu),如ConcurrentHashMap
、CopyOnWriteArrayList
等。Map<String, String> concurrentMap = new ConcurrentHashMap<>(); List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
-
使用
Lock
和Condition
: 使用Lock
接口及其實(shí)現(xiàn)類來提供更細(xì)粒度的同步控制。Lock lock = new ReentrantLock(); Condition condition = lock.newCondition();lock.lock(); try {// 臨界區(qū)的代碼 } finally {lock.unlock(); }
-
使用
volatile
關(guān)鍵字:volatile
關(guān)鍵字可以保證變量的可見性,但不能解決復(fù)合操作的原子性問題。private volatile boolean flag = false;
-
使用原子類:
java.util.concurrent.atomic
包中提供了一些原子類,如AtomicInteger
、AtomicLong
等,用于執(zhí)行原子操作。AtomicInteger atomicInt = new AtomicInteger(0); atomicInt.incrementAndGet();
確保線程安全是一個(gè)綜合性的問題,需要在設(shè)計(jì)階段考慮,并采用適當(dāng)?shù)耐酱胧_x擇合適的同步機(jī)制取決于具體的應(yīng)用場(chǎng)景和性能要求。在設(shè)計(jì)和實(shí)現(xiàn)多線程程序時(shí),充分了解并考慮線程安全性是至關(guān)重要的。
實(shí)例5:開發(fā)線程安全的火車售票系統(tǒng)
6.2線程同步機(jī)制
線程同步機(jī)制是一組用于確保多個(gè)線程訪問共享資源時(shí)不會(huì)發(fā)生競(jìng)態(tài)條件和數(shù)據(jù)不一致的技術(shù)。以下是一些常見的線程同步機(jī)制:
-
synchronized 關(guān)鍵字:
synchronized
關(guān)鍵字用于修飾方法或代碼塊,確保在同一時(shí)刻最多只有一個(gè)線程能夠進(jìn)入被synchronized
修飾的方法或代碼塊。
// 同步方法 public synchronized void synchronizedMethod() {// 同步的代碼塊 }// 同步代碼塊 public void someMethod() {// 非同步代碼synchronized (lockObject) {// 同步的代碼塊}// 非同步代碼 }
-
Lock 和 Condition 接口:
Lock
接口提供了比synchronized
更靈活的鎖定機(jī)制。通過ReentrantLock
實(shí)現(xiàn)類,可以使用lock()
和unlock()
方法來控制臨界區(qū)的訪問。Condition
接口用于在Lock
上創(chuàng)建條件變量,通過await()
、signal()
和signalAll()
方法實(shí)現(xiàn)更靈活的線程通信。
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition();lock.lock(); try {// 臨界區(qū)的代碼 } finally {lock.unlock(); }
-
volatile 關(guān)鍵字:
volatile
關(guān)鍵字用于聲明變量,確保線程之間對(duì)該變量的寫入和讀取操作是可見的。它不提供原子性,僅僅保證了可見性。
private volatile boolean flag = false;
-
Atomic 類:
java.util.concurrent.atomic
包中提供了一組原子類,如AtomicInteger
、AtomicLong
,用于執(zhí)行原子操作,避免競(jìng)態(tài)條件。
AtomicInteger atomicInt = new AtomicInteger(0); atomicInt.incrementAndGet();
-
ReadWriteLock 接口:
ReadWriteLock
接口提供了讀寫鎖,允許多個(gè)線程同時(shí)讀取共享資源,但只允許一個(gè)線程寫入。
ReadWriteLock rwLock = new ReentrantReadWriteLock(); rwLock.readLock().lock(); // 讀取共享資源的操作 rwLock.readLock().unlock();rwLock.writeLock().lock(); // 寫入共享資源的操作 rwLock.writeLock().unlock();
這些機(jī)制可以根據(jù)具體的應(yīng)用場(chǎng)景選擇使用,每種機(jī)制都有其適用的情況。合理選擇同步機(jī)制可以提高多線程程序的性能和可維護(hù)性,避免潛在的并發(fā)問題。