為避免死鎖、數據競爭與異常安全問題,同步塊/方法內應僅執行簡單、確定性的操作(讀寫字段、集合操作),嚴禁調用可覆寫方法、回調、外部 API、耗時 I/O。
// 觀察者介面
public interface SetObserver<E> {
void added(ObservableSet<E> set, E element);
}
// 被觀察集合:同步區內直接回調
public class ObservableSet<E> extends ForwardingSet<E> {
private final List<SetObserver<E>> observers = new ArrayList<>();
public void addObserver(SetObserver<E> o) {
synchronized (observers) { observers.add(o); }
}
public boolean removeObserver(SetObserver<E> o) {
synchronized (observers) { return observers.remove(o); }
}
// 危險:持有鎖遍歷並調用外來方法
private void notifyElementAdded(E element) {
synchronized (observers) { // 持有鎖
for (SetObserver<E> o : observers) { // 遍歷
o.added(this, element); // 調用外來方法(可覆寫、不可控)
}
}
}
@Override public boolean add(E e) {
boolean added = super.add(e);
if (added) notifyElementAdded(e);
return added;
}
}
ConcurrentModificationExceptionset.addObserver(new SetObserver<Integer>() {
public void added(ObservableSet<Integer> s, Integer e) {
System.out.println(e);
if (e == 23) s.removeObserver(this); // 回調中修改正在被遍歷的 observers 列表
}
});
// 拋出 ConcurrentModificationException
set.addObserver(new SetObserver<Integer>() {
public void added(ObservableSet<Integer> s, Integer e) {
if (e == 23) {
ExecutorService exec = Executors.newSingleThreadExecutor();
try {
exec.submit(() -> s.removeObserver(this)).get(); // 子線程嘗試獲取 observers 鎖
} catch (Exception ex) { throw new RuntimeException(ex); }
finally { exec.shutdown(); }
}
}
});
// 主線程持有鎖調用回調 → 子線程阻塞等待鎖 → 主線程等待子線程結束(get()) → 死鎖
// 安全:同步區內僅複製引用,釋放鎖後遍歷調用
private void notifyElementAddedSafe(E element) {
List<SetObserver<E>> snapshot;
synchronized (observers) { // 1. 僅複製,極短持鎖
snapshot = new ArrayList<>(observers);
}
for (SetObserver<E> o : snapshot) { // 2. 無鎖遍歷調用
o.added(this, element);
}
}
優勢:
observers 不影響快照遍歷,無 CME。observers 鎖不會死鎖(主線程已釋放)。CopyOnWriteArrayList(JUC 線程安全容器)// 寫時複製,讀無鎖,天然適合「讀多寫少」觀察者列表
private final List<SetObserver<E>> observers = new CopyOnWriteArrayList<>();
public void addObserver(SetObserver<E> o) { observers.add(o); }
public boolean removeObserver(SetObserver<E> o) { return observers.remove(o); }
private void notifyElementAdded(E element) {
for (SetObserver<E> o : observers) { // 直接遍歷,無需同步
o.added(this, element);
}
}
適用場景:觀察者數量較少、添加/移除頻率低、遍歷頻率高。寫入會複製底層數組,大量寫入時效能下降。
10000) throw new RuntimeException("超額"); // 同步外驗證
synchronized (this) {
from.balance -= amount;
to.balance += amount;
}
}
| 禁止在同步區內 | 允許/推薦在同步區內 |
|---|---|
| 調用可覆寫方法(回調、接口方法、抽象方法) | 讀寫基本類型字段、volatile 字段 |
| 調用外部庫/第三方 API | 集合增刪改查(add/remove/get/put) |
| I/O 操作(網絡、磁盤、資料庫) | 簡單計算、邏輯判斷 |
| 長時間計算/睡眠 | 複製集合引用/創建快照 |
| 嘗試獲取其他鎖(嵌套鎖風險) | 釋放鎖前的最後檢查 |
CopyOnWriteArrayList。ReentrantLock、StampedLock、分段鎖、ThreadLocal、無鎖結構替代大範圍 synchronized。「過度同步」不僅是效能殺手,更是死鎖、活鎖、數據不一致的溫床。將同步區視為臨界區保護共享狀態的最後一道防線,而非業務邏輯的容器,是寫出高併發健壯 Java 代碼的關鍵心法。