在上一篇《Java 101 線程基礎》中,我們介紹了線程的基本概念與使用方式。本文將深入探討 Java 線程的缺陷與副作用,並分析 Sun JDK 1.2 如何改進線程運行機制。
線程允許多個任務並發執行,並可共享對象與數據。然而,多線程同時存取同一數據會導致數據不一致。Java 提供 synchronized 關鍵字,可應用於方法或代碼塊,確保同一時間僅有一個線程執行該段代碼,其他線程將被阻塞直到釋放鎖。
定義同步方法的語法:
[public|private] synchronized {returnType} methodName(...) { ... }
同步代碼塊示例:
public class SomeClass {
public void aMethod() {
// 非同步代碼
synchronized(this) {
// 同步代碼塊
}
// 更多非同步代碼
}
}
多線程編程中最常見的兩大問題是死鎖與飢餓。
當兩個或多個線程互相持有對方需要的資源,導致彼此永遠等待,即發生死鎖。經典例子:你在 ATM 取款,卻提示「無現金」;運鈔車要加錢卻被你的車擋住;你不取到錢不走,運鈔車不加錢不走——雙方陷入永久等待。
死鎖發生的四個必要條件(缺一不可):
破壞任一條件即可避免死鎖。例如:線程發現無法獲得第二資源時,主動釋放第一資源。
系統未死鎖,但某些線程長期無法獲得 CPU 時間或資源,導致無法推進。飢餓無固定觸發規則,常見於優先級調度不當或資源分配策略缺陷。
當主線程需等待子線程完成並獲取結果時,可使用 join():
CompleteCalcThread t = new CompleteCalcThread();
t.start();
// 做其他事...
t.join(); // 等待 t 結束
// 使用計算結果...
join() 有三種重載:無參無限等待、指定毫秒超時、指定毫秒+納秒超時。配合 isAlive() 可判斷線程是否仍在運行。
實現生產者-消費者模式的核心。生產者向共享列表添加項目後調用 notify() 喚醒消費者;消費者調用 wait() 等待通知。注意:wait/notify 必須在 synchronized 方法或塊中調用。
偽代碼結構:
class ProdCons {
class List {
public synchronized boolean add(Object o) {...}
public synchronized boolean remove(Object o) {...}
}
List data = new List();
ProdThread producer;
ConsThread consumer;
ProdCons() {
producer = new ProdThread(this);
consumer = new ConsThread(this);
producer.start();
consumer.start();
}
}
早期線程實現存在缺陷,導致 stop()、suspend()、resume() 方法在 JDK 1.2 中被批評並最終棄用。主要問題:
解決方案:自定義線程類,通過狀態標誌協作式暫停/停止,而非強制中斷。示例 MyThread 類:
public class MyThread extends Thread {
static final int STATE_RUNNING = 0;
static final int STATE_STOP = 1;
static final int STATE_SUSPEND = 2;
private int currentState = STATE_RUNNING;
public final synchronized void changeState(int newState) {
currentState = newState;
if (STATE_RUNNING == currentState) notify();
}
private synchronized boolean checkState() {
while (STATE_SUSPEND == currentState) {
try { wait(); } catch (Exception e) {}
}
return STATE_STOP != currentState;
}
public void run() {
do {
if (!checkState()) return;
// 執行實際工作
} while (true);
}
}
JDK 1.2 將線程安全責任轉移給開發者:需自行確保線程關閉時釋放資源、避免不一致、正常終止。
線程功能強大但複雜。理解同步、死鎖、飢餓及協作式控制,能幫助開發者編寫更健壯的多線程應用。隨著 JDK 演進,掌握現代並發工具(如 java.util.concurrent)同樣重要。