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

Java 同步陷阱:切勿重新分配被鎖定對象的引用

     編輯:Java 程式設計入門

同步機制的核心誤區

在 Java 多線程程式設計中,synchronized 關鍵字是實現線程安全的基礎工具。然而,許多開發者忽略了一個關鍵細節:同步鎖定的是對象實例,而非變量引用本身。當你在同步塊內部重新分配被鎖定對象的引用時,會導致嚴重的線程安全問題。

問題代碼示例:動態擴容的 Stack 實現

考慮以下使用數組實現的棧結構,當容量不足時會重新分配更大的數組:

class Stack {
    private int stackSize = 10;
    private int[] intArr = new int[stackSize];
    private int index = 0;

    public void push(int val) {
        synchronized(intArr) {
            if (index == intArr.length) {
                stackSize *= 2;
                int[] newIntArr = new int[stackSize];
                System.arraycopy(intArr, 0, newIntArr, 0, intArr.length);
                intArr = newIntArr;  // 危險:重新分配引用
            }
            intArr[index] = val;
            index++;
        }
    }

    public int pop() {
        synchronized(intArr) {
            if (index > 0) {
                return intArr[--index];
            }
        }
        throw new EmptyStackException();
    }
}

錯誤場景推演

  1. 線程 1 調用 push(),獲得 intArr 當前引用的數組對象鎖
  2. 線程 1 執行擴容操作,將 intArr 指向新數組
  3. 線程 1 釋放舊數組的鎖
  4. 線程 2 調用 pop(),獲得舊數組的鎖(因為它在擴容前就已等待)
  5. 線程 1 再次調用 push(),獲得新數組的鎖
  6. 結果:兩個線程持有不同對象的鎖,同時訪問邏輯上同一個棧,導致數據不一致

正確解決方案

將同步粒度提升到方法層級,鎖定 Stack 實例本身(this),而非內部數組引用:

class Stack {
    private int stackSize = 10;
    private int[] intArr = new int[stackSize];
    private int index = 0;

    public synchronized void push(int val) {
        if (index == intArr.length) {
            stackSize *= 2;
            int[] newIntArr = new int[stackSize];
            System.arraycopy(intArr, 0, newIntArr, 0, intArr.length);
            intArr = newIntArr;
        }
        intArr[index++] = val;
    }

    public synchronized int pop() {
        if (index > 0) {
            return intArr[--index];
        }
        throw new EmptyStackException();
    }
}

核心原則

  • 鎖定不變的對象引用:通常使用 this、私有 final 對象或專用鎖對象
  • 避免在同步塊中修改鎖對象引用:引用變更會導致線程持有不同鎖
  • 方法級同步優於塊級同步:當保護整個對象狀態時,synchronized 方法更安全且可讀性更高

這個陷阱提醒我們:同步保護的是對象身份,而非變量名稱。任何可能導致鎖對象身份改變的操作,都會破壞同步語義。

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