synchronized 是 Java 多執行緒同步的核心關鍵字,雖然使用看似簡單,但其底層鎖機制、作用範圍與潛在陷阱卻常被忽視。本文將從鎖的本質、不同使用場景的行為差異、最佳實踐等方面進行深度剖析。
無論 synchronized 修飾方法還是代碼塊,它獲取的鎖始終是對象,而非某段代碼或方法。每個對象只有一把鎖。理解這一點是避免同步失效的關鍵。
public synchronized void methodAAA() {
// 同步代碼
}
等價於:
public void methodAAA() {
synchronized (this) {
// 同步代碼
}
}
鎖對象:調用該方法的實例對象(如 P1)。
行為:同一對象的不同執行緒間互斥;但不同對象(如 P2)可同時調用該方法,互不干擾。若共享數據屬於類層級(static),此方式無法保護。
public void method3(SomeObject so) {
synchronized (so) {
// 同步代碼
}
}
鎖對象:括號內指定的對象 so。誰持有這把鎖,誰就能執行塊內代碼。
技巧:無明確鎖對象時,可創建專用 private byte[] lock = new byte[0]; 作為鎖。零長度 byte 陣列比 new Object() 更輕量(字節碼指令更少)。
class Foo {
public synchronized static void staticMethod() { ... }
public void instanceMethod() {
synchronized (Foo.class) { ... } // 等價於上方
}
}
鎖對象:當前類的 Class 對象(Foo.class)。
關鍵差異:靜態同步方法鎖住 Class 對象,實例同步方法鎖住實例對象(this)。兩者鎖不同,互不阻塞。同一對象同時訪問靜態同步方法與實例同步方法不會同步。
P1.getClass() 可能返回子類的 Class 對象,而非 Foo.class,導致鎖不一致。請直接使用 Foo.class。
clone() 或不可變視圖,避免內部狀態被外部竄改。private List list = new ArrayList<>();
public synchronized List getList() {
return new ArrayList<>(list); // 返回副本
}
java.util.concurrent.locks.ReentrantLock 等更靈活的鎖實現,複雜場景優先考慮。搞清楚 synchronized 鎖定的是哪個對象,是編寫正確多執行緒程式的基石。實例方法鎖 this,靜態方法鎖 Class 對象,同步塊鎖指定對象。封裝共享狀態、縮小同步粒度、避免死鎖,是實戰核心原則。