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

Java 並發核心:深入解析 synchronized 關鍵字與鎖機制

     編輯:Java 綜合教學

Java 並發核心:深入解析 synchronized 關鍵字與鎖機制

在 Java 多線程程式設計中,synchronized 是實現線程同步最基礎、最常用的關鍵字。它就像一把「互斥鎖」,確保同一時間只有一個線程能夠訪問受保護的代碼區域,從而避免數據競爭與不一致問題。本文將從底層原理、使用場景、字節碼層面以及常見誤區等多個維度,全面剖析 synchronized 的核心機制。

一、 核心原理:Monitor 與對象鎖

Java 中的每一個對象在內存中都關聯著一個 Monitor(監視器/管程),這就是所謂的「對象鎖」。當多個線程嘗試同時訪問同一個對象的同步代碼時,JVM 會要求線程先獲取該對象的 Monitor。只有成功獲取鎖的線程才能進入同步區執行;其他線程則會進入阻塞隊列等待,直到持有鎖的線程釋放鎖(正常退出或拋出異常)為止。

二、 兩種基本使用形式

1. 同步實例方法

public synchronized void doSomething() {
    // 臨界區代碼
}

鎖對象是 this(當前實例對象)。

2. 同步代碼塊

public void doSomething() {
    synchronized(this) { // 或 synchronized(lockObject)
        // 臨界區代碼
    }
}

鎖對象由開發者指定,靈活性更高,粒度更細,能有效減少同步範圍,提升並發性能。

3. 同步靜態方法

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

鎖對象是當前類的 Class 對象(即 CurrentClass.class),屬於類級別的鎖,與實例鎖互不干擾。

三、 關鍵行為規則(必須牢記)

  1. 同一對象,同步互斥: 線程 A 正在執行對象 obj 的同步方法時,線程 B 無法同時執行 obj 的任何其他同步方法(無論方法名是否相同),因為它們共享同一把鎖(this 或指定對象)。
  2. 同步不阻塞非同步: 線程 A 持有對象鎖執行同步方法時,線程 B 仍可隨時調用該對象的 非同步方法,因為非同步方法不需要獲取鎖。
  3. 不同對象,鎖互不相干: 線程 A 訪問 obj1 的同步方法,線程 B 訪問 obj2 的同步方法(即使是同一類的實例),兩者並行無阻塞,因為鎖屬於不同對象實例。

四、 字節碼視角:monitorenter 與 monitorexit

通過 javap -c 反編譯字節碼可見,synchronized 的實現依賴兩條指令:

  • monitorenter:進入同步塊前嘗試獲取鎖(計數器 +1)。
  • monitorexit:退出同步塊時釋放鎖(計數器 -1),編譯器會在正常退出路徑和異常處理路徑分別插入此指令,保證鎖必定被釋放。

這也是為什麼 synchronized 不會因異常導致死鎖的原因——JVM 幫我們兜底了。

五、 鎖優化與進階知識點

1. 鎖粒度選擇:方法鎖 vs 代碼塊鎖

若方法中只有小部分涉及共享資源,優先使用 同步代碼塊 縮小鎖範圍,避免不必要的阻塞,提高吞吐量。

2. 類鎖與對象鎖互不干擾

class Demo {
    public synchronized void instanceMethod() { ... } // 鎖 this
    public static synchronized void staticMethod() { ... } // 鎖 Demo.class
}

一個線程執行 instanceMethod,另一個線程執行 staticMethod,不會互斥,因為鎖對象不同。

3. 顯式指定類鎖的寫法

public void methodBBB() {
    synchronized(Demo.class) { // 等價於 static synchronized
        // ...
    }
}

六、 synchronized 的局限性與演進

雖然 synchronized 經過 JDK 1.6+ 的大量優化(偏向鎖、輕量級鎖、鎖消除、鎖粗化、自旋鎖等),性能已大幅提升,但仍存在固有缺陷:

  • 無法中斷等待: 線程一旦進入阻塞狀態等待鎖,無法響應 interrupt(),只能被動等待。
  • 無超時機制: 不支持「嘗試獲取鎖,超時則放棄」的邏輯。
  • 無公平性保證: 默認非公平鎖,可能導致線程饑餓。
  • 單一條件隊列: 僅支持一個 wait/notify 隊列,無法實現多條件變量精細控制。

為解決上述問題,JDK 1.5 引入了 java.util.concurrent.locks.Lock 接口及其實現類 ReentrantLock,提供了可中斷、可超時、公平/非公平可選、多條件變量等豐富功能,成為高並發場景下的首選。

七、 最佳實踐總結

  1. 優先使用同步代碼塊,鎖定具體共享資源對象,而非整個方法或 this。
  2. 避免在同步塊中調用耗時操作(I/O、睡眠、外部服務調用),縮短持鎖時間。
  3. 不可變對象(如 String、包裝類)不適合作為鎖對象,因常量池緩存可能導致非預期共享鎖。
  4. 高併發、需可中斷/超時/公平控制時,果斷選擇 ReentrantLock 或 ReadWriteLock。
  5. 善用 JDK 內置的線程安全集合(ConcurrentHashMap、CopyOnWriteArrayList 等)與原子類(AtomicInteger 等),減少手動同步需求。

掌握 synchronized 的本質與邊界,是通往 Java 並發編程高手之路的必修課。理解其底層 Monitor 機制、字節碼語義及 JVM 優化手段,才能在實戰中做出正確的架構決策。

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