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

Java 多執行緒同步機制深度解析:synchronized、鎖升級、最佳實務

     編輯:Java 相關知識

前言

多執行緒環境下,共享資源的並發存取若缺乏同步,必然導致資料競爭、可見性失效、指令重排等問題。Java 提供 synchronized 關鍵字作為內建同步原語,經歷 JDK 1.6 鎖優化(偏向鎖、輕量級鎖、重量級鎖)後,效能已大幅提升。本文系統剖析同步原理、鎖類型、代碼塊與方法差異、常見陷阱與現代替代方案。

核心概念:內建鎖

  • 每個 Java 物件天生擁有一把 **監視器鎖**,又稱內建鎖或監視器。
  • synchronized 作用於:實例方法(鎖 this)、靜態方法(鎖 Class 物件)、同步代碼塊(鎖指定物件)。
  • 鎖具備 **可重入性**:同一執行緒可重複獲得同一把鎖。

三種同步形式對比

形式鎖對象粒度適用場景
synchronized void method()當前實例 (this)粗(整個方法)簡單業務、方法邏輯緊湊
static synchronized void method()當前類 Class 物件粗(類級別)靜態資源初始化、單例雙重檢查
synchronized(obj) { ... }指定物件 obj細(僅代碼塊)高併發熱點、非 this 鎖、條件等待

同步代碼塊最佳寫法

public class BankAccount {
    private final Object balanceLock = new Object(); // 專用鎖,封裝性佳
    private long balance;

    public void deposit(long amount) {
        synchronized (balanceLock) { // 縮小同步範圍
            balance += amount;
        }
    }

    public void transfer(BankAccount target, long amount) {
        // 避免死鎖:固定獲取鎖順序(hashCode 排序)
        Object first = this.balanceLock;
        Object second = target.balanceLock;
        if (System.identityHashCode(first) > System.identityHashCode(second)) {
            first = target.balanceLock;
            second = this.balanceLock;
        }
        synchronized (first) {
            synchronized (second) {
                this.balance -= amount;
                target.balance += amount;
            }
        }
    }
}

JDK 1.6+ 鎖升級機制

  1. 無鎖狀態:物件頭 Mark Word 儲存 hashCode、分代年齡等。
  2. 偏向鎖:首次獲得鎖的執行緒在 Mark Word 記錄 Thread ID,後續進入無需 CAS,極低開銷。適用:無競爭或單執行緒。
  3. 輕量級鎖:競爭出現,執行緒嘗試 CAS 將 Mark Word 指向棧框中的 Lock Record。適用:輕度競爭、短關鍵區。
  4. 重量級鎖:CAS 失敗膨脹為 ObjectMonitor,執行緒進入 OS 內核等待隊列。適用:高併發、長關鍵區。

鎖只能升級不能降級,但 JVM 會在安全點嘗試撤銷偏向鎖、膨脹輕量級鎖。

常見同步陷阱

  1. 鎖住非 final 物件:synchronized (list) { list = new ArrayList(); } 鎖失效,應鎖 final 物件。
  2. 字串常量當鎖:synchronized("lock"),常量池共享導致非預期阻塞。
  3. 同步塊過大:包含 I/O、睡眠、長計算,嚴重降低併發度。
  4. 雙重檢查鎖單例:需 volatile 禁止指令重排,否則可能返回半初始化物件。

現代替代方案對比

方案優點缺點適用
ReentrantLock可中斷、公平鎖、條件變數、嘗試獲取需手動 unlock、語法較繁複雜同步邏輯、需超時控制
ReadWriteLock讀讀並行、寫獨佔寫鎖饑餓風險讀多寫少(緩存、配置)
StampedLock樂觀讀、無寫時無鎖不支援條件變數、不可重入高讀併發、可容忍臨時不一致
Atomic*無鎖 CAS、效能極高僅支援單變數操作計數器、旗標、累加器
ConcurrentHashMap分段鎖/CAS、高併發 Mapsize() 弱一致共享快取、註冊表

實戰範例:生產者-消費者模式

public class BoundedBuffer<T> {
    private final T[] buffer;
    private int head, tail, count;
    private final Object lock = new Object();

    @SuppressWarnings("unchecked")
    public BoundedBuffer(int capacity) {
        buffer = (T[]) new Object[capacity];
    }

    public void put(T item) throws InterruptedException {
        synchronized (lock) {
            while (count == buffer.length) lock.wait(); // 滿等待
            buffer[tail] = item;
            tail = (tail + 1) % buffer.length;
            count++;
            lock.notifyAll();
        }
    }

    public T take() throws InterruptedException {
        synchronized (lock) {
            while (count == 0) lock.wait(); // 空等待
            T item = buffer[head];
            buffer[head] = null;
            head = (head + 1) % buffer.length;
            count--;
            lock.notifyAll();
            return item;
        }
    }
}

結語

synchronized 經過多版本優化,仍是最通用、可讀性最高的同步手段。理解鎖升級、避免常見陷阱、善用 java.util.concurrent 高階工具,是寫出高效能、無 Bug 並發程式的關鍵。記住:**同步範圍越小越好,鎖物件越私有越好,業務邏輯越簡單越好。**

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