首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 同步機制深度解析:synchronized 關鍵字對多執行緒的影響與最佳實踐

     編輯:Java 相關知識

Java 內建的 synchronized 關鍵字是多執行緒同步的基石,但許多開發者僅知「加上就能解決同步問題」,卻不清楚其鎖定對象、作用範圍與潛在陷阱。本文深入剖析 synchronized 在不同位置的語意差異、鎖定物件機制,並提供安全共享資源的實戰技巧。

核心觀念三要素

  1. 鎖永遠是物件:無論加在方法或區塊,synchronized 鎖定的都是物件,而非程式碼片段。
  2. 每個物件僅有一把鎖:同一物件的所有同步方法/區塊共用同一把鎖。
  3. 同步有代價:系統開銷大、可能造成死鎖,應盡量縮小同步範圍、避免不必要同步。

四種使用場景與鎖定對象分析

1. 同步實例方法


public synchronized void methodAAA() { ... }

等價於:


public void methodAAA() {
    synchronized (this) { ... } // 鎖定「呼叫該方法的物件實例」
}

行為:同一物件(如 P1)的多執行緒互斥;不同物件(P1 vs P2)互不干擾,可同時執行。

2. 同步區塊(指定物件)


public void method3(SomeObject so) {
    synchronized (so) { ... } // 鎖定傳入的 so 物件
}

應用:需明確控制鎖對象時使用。若無現成物件,可建立專用鎖物件:


private byte[] lock = new byte[0]; // 零長度 byte 陣列:建構開銷最小(3 位元組碼 vs Object 7 位元組碼)
public void methodA() { synchronized(lock) { ... } }

3. 同步靜態方法


public synchronized static void methodAAA() { ... }

等價於:


public void methodBBB() {
    synchronized (Foo.class) { ... } // 鎖定「類別物件」(Class 實例)
}

關鍵差異:鎖定的是 Class 物件,而非實例物件。P1.getClass() 雖同樣回傳 Foo.class,但語意上應直接使用 Foo.class。

4. 同步類別字面常量

即上述 synchronized (Foo.class),效果與同步靜態方法相同。

關鍵推論:靜態 vs 實例同步互不干擾

若類別同時有 synchronized static void A() 和 synchronized void B():

  • A 鎖定 Foo.class(類別鎖)
  • B 鎖定 this(實例鎖)
  • 同一物件的多執行緒同時呼叫 A 和 B 時,不會互斥,因為鎖不同!

安全共享資源的實戰技巧

技巧 1:封裝可變狀態,提供同步存取器


// 錯誤:public 欄位可繞過同步直接修改
public int count;

// 正確:private 欄位 + 同步 getter/setter
private int count;
public synchronized int getCount() { return count; }
public synchronized void setCount(int c) { count = c; }

技巧 2:可變物件(集合、陣列)需深度防禦

即使欄位是 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)
}

技巧 3:縮小同步範圍


// 粗粒度:整個方法同步
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 等)取代低層同步,獲得更佳效能與表達力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved