Java 多線程的基石是 **對象隱式鎖(Monitor)**,synchronized 是使用它的唯一語法。本文從底層原理切入,揭示 wait/notify 必須在同步塊中的原因,並實戰分析兩種經典死鎖場景。
wait() 會將**所有**線程放入等待集,卻無線程持有鎖去 notify() → 永久阻塞。synchronized 保證**同一時刻僅一線程持有鎖**,該線程 wait() 時**釋放鎖**,其它線程才能進入並喚醒它。ReentrantLock+Condition 已解決)。class DeadLockSample {
public final Object lock1 = new Object();
public final Object lock2 = new Object();
public void methodOne() {
synchronized (lock1) {
synchronized (lock2) { /* ... */ }
}
}
public void methodTwo() {
synchronized (lock2) {
synchronized (lock1) { /* ... */ }
}
}
}
線程 A 持有 lock1 等 lock2,線程 B 持有 lock2 等 lock1 → **循環等待,死鎖**。
class WaitAndNotify {
final List<String> list = new LinkedList<>();
public synchronized void push(String x) {
synchronized (list) { list.add(x); notify(); }
}
public synchronized Object pop() {
synchronized (list) {
if (list.isEmpty()) {
try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
return list.size();
}
}
}
**死鎖過程**:
pop(),持有 this 鎖與 list 鎖,wait() 釋放 this 鎖,**但仍持有 list 鎖**。
2. 線程 T2 獲取 this 鎖進入 push(),嘗試獲取 list 鎖 → **永久阻塞**。
3. T1 等待 notify(),T2 等待 list 鎖 → **雙方僵持**。
根因:wait() 僅釋放**當前對象**的鎖,無法像棧桢式釋放所有嵌套鎖。
synchronized 是進入並發世界的大門,但並非萬能鑰匙。Thread 的 daemon 與 join 協作機制。