多線程環境下,線程間如何協作、共享資料且保證線程安全,是 Java 並發編程的核心課題。本文從「為何出現安全問題」切入,層層遞進講解 synchronized、wait/notify、wait/notifyAll 的運作原理,並透過「生產者-消費者」經典模型展示最佳實踐代碼。
i++ 實為讀-改-寫三步)。缺一不可。單線程或無共享資源皆無安全隱患。
synchronized 只能保證互斥訪問,無法控制線程執行順序。線程釋放鎖後仍參與下一輪搶佔,可能導致「同步中的不同步」:生產者連續生產、消費者無法及時消費,或反之。
| 方法 | 所屬類 | 調用前提 | 行為 |
|---|---|---|---|
wait() | Object | 持有該對象鎖(在 synchronized 內) | 釋放鎖,進入等待集合,阻塞直到被喚醒或超時 |
notify() | Object | 持有該對象鎖 | 隨機喚醒一個在該對象上等待的線程 |
notifyAll() | Object | 持有該對象鎖 | 喚醒所有在該對象上等待的線程 |
核心規則:等待與喚醒必須針對同一把鎖,否則會拋出 IllegalMonitorStateException。
class Resource {
String name, sex;
boolean flag = false; // true=已生產,待消費
}
class Input implements Runnable {
private Resource r;
Input(Resource r) { this.r = r; }
public void run() {
int x = 0;
while (true) {
synchronized (r) {
if (r.flag) {
try { r.wait(); } catch (InterruptedException e) {}
}
if (x == 0) { r.name = "mike"; r.sex = "man"; }
else { r.name = "麗麗"; r.sex = "女"; }
x = (x + 1) % 2;
r.flag = true;
r.notify(); // 喚醒消費者
}
}
}
}
class Output implements Runnable {
private Resource r;
Output(Resource r) { this.r = r; }
public void run() {
while (true) {
synchronized (r) {
if (!r.flag) {
try { r.wait(); } catch (InterruptedException e) {}
}
System.out.println(r.name + "...." + r.sex);
r.flag = false;
r.notify(); // 喚醒生產者
}
}
}
}
class Resource {
private String name, sex;
private boolean flag = false;
public synchronized void set(String name, String sex) {
if (flag) { try { wait(); } catch (InterruptedException e) {} }
this.name = name; this.sex = sex;
flag = true;
notify();
}
public synchronized void out() {
if (!flag) { try { wait(); } catch (InterruptedException e) {} }
System.out.println(name + "........" + sex);
flag = false;
notify();
}
}
class Input implements Runnable {
private Resource r; int x = 0;
Input(Resource r) { this.r = r; }
public void run() {
while (true) {
if (x == 0) r.set("mike", "man");
else r.set("麗麗", "女");
x = (x + 1) % 2;
}
}
}
class Output implements Runnable {
private Resource r;
Output(Resource r) { this.r = r; }
public void run() { while (true) r.out(); }
}
優化點:同步邏輯內聚於 Resource,業務線程僅呼叫高層方法,降低耦合、提升可讀性。
while (condition) wait() 循環檢查條件,雖稍耗效能但更安全、通用,推薦生產環境使用。while (條件) wait() 循環中調用 wait(),防止虛假喚醒。private,提供同步方法封裝協作邏輯。notifyAll(),除非極度確定等待集合單一角色。java.util.concurrent 套件(BlockingQueue、Semaphore、Condition)替代手工 wait/notify。wait/notify 是 Java 線程協作的基石,理解「鎖即條件、條件即鎖」的設計哲學,配合生產者-消費者模型,即可構建正確高效的多線程協作系統。實戰中建議優先採用 J.U.C 高階組件,降低底層同步錯誤風險。