Java 內建的 synchronized 關鍵字是多執行緒同步的基石,但許多開發者僅知「加上就能解決同步問題」,卻不清楚其鎖定對象、作用範圍與潛在陷阱。本文深入剖析 synchronized 在不同位置的語意差異、鎖定物件機制,並提供安全共享資源的實戰技巧。
synchronized 鎖定的都是物件,而非程式碼片段。
public synchronized void methodAAA() { ... }
等價於:
public void methodAAA() {
synchronized (this) { ... } // 鎖定「呼叫該方法的物件實例」
}
行為:同一物件(如 P1)的多執行緒互斥;不同物件(P1 vs P2)互不干擾,可同時執行。
public void method3(SomeObject so) {
synchronized (so) { ... } // 鎖定傳入的 so 物件
}
應用:需明確控制鎖對象時使用。若無現成物件,可建立專用鎖物件:
private byte[] lock = new byte[0]; // 零長度 byte 陣列:建構開銷最小(3 位元組碼 vs Object 7 位元組碼)
public void methodA() { synchronized(lock) { ... } }
public synchronized static void methodAAA() { ... }
等價於:
public void methodBBB() {
synchronized (Foo.class) { ... } // 鎖定「類別物件」(Class 實例)
}
關鍵差異:鎖定的是 Class 物件,而非實例物件。P1.getClass() 雖同樣回傳 Foo.class,但語意上應直接使用 Foo.class。
即上述 synchronized (Foo.class),效果與同步靜態方法相同。
若類別同時有 synchronized static void A() 和 synchronized void B():
Foo.class(類別鎖)this(實例鎖)
// 錯誤:public 欄位可繞過同步直接修改
public int count;
// 正確:private 欄位 + 同步 getter/setter
private int count;
public synchronized int getCount() { return count; }
public synchronized void setCount(int c) { count = c; }
即使欄位是 private 且 getter 加上 synchronized,呼叫端拿到參考仍可修改內部狀態:
// 危險:回傳原始參考
private List items = new ArrayList<>();
public synchronized List getItems() { return items; }
// 安全:回傳防禦性副本
public synchronized List getItems() {
return new ArrayList<>(items); // 或 Collections.unmodifiableList(items)
}
// 粗粒度:整個方法同步
public synchronized void process() {
longTask(); // 耗時操作持有鎖太久
updateState();
}
// 細粒度:僅同步關鍵區段
public void process() {
longTask(); // 無鎖執行
synchronized(this) { updateState(); } // 僅保護狀態更新
}
| 陷阱 | 說明 | 對策 |
|---|---|---|
| 以為同步方法能防止所有並發問題 | 不同物件實例互不阻擋 | 使用共享鎖物件或靜態同步 |
| 在同步區塊中呼叫外部/不可控代碼 | 易造成死鎖、效能劣化 | 同步區塊僅執行單純狀態變更 |
| 使用 String 常量作為鎖 | 字串常量池導致非預期共享鎖 | 使用 new Object() 或私有 final 物件 |
| 雙重檢查鎖定 (DCL) 失效 | 指令重排序導致單例模式失效 | 使用 volatile 或 static holder 模式 |
掌握 synchronized 的核心在於「識別鎖物件」。同步實例方法鎖 this,靜態方法鎖 Class,區塊鎖指定物件。配合封裝性、防禦性副本、細粒度鎖定等技巧,才能寫出高效、安全的多執行緒程式。現代開發亦建議善用 java.util.concurrent 套件(ReentrantLock、ConcurrentHashMap、AtomicInteger 等)取代低層同步,獲得更佳效能與表達力。