首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Java ReentrantLock 多線程計數丟失問題:實例共享與鎖機制深度解析

     編輯:程式設計疑難解答

問題現象

使用 ReentrantLock 保護共享變量 a 進行自增操作,啟動 3 個線程各執行 100 次自增,預期最終值為 300,但實際運行結果經常「丟數字」,最終值小於 300。

根因分析

原代碼在每個線程的 run() 方法中 新建了獨立的 MulThreadTest 實例:

new Thread(new Runnable() {
    @Override
    public void run() {
        MulThreadTest test = new MulThreadTest(); // 每個線程各自一份 test
        for (int j = 0; j < 100; j++) test.addInt();
    }
}, "thread" + i).start();

導致:

  • 每個實例擁有獨立的 lock 對象(非 static),鎖無法跨實例互斥
  • 雖然 a 是 static 共享,但鎖保護失效,多線程並發修改 a 產生競態條件

正確修復方案

確保所有線程操作 同一把鎖、同一個共享變量:

public class MulThreadTest {
    public static int a = 0;
    // 鎖也應聲明為 static,確保類級別共享
    private static final ReentrantLock lock = new ReentrantLock(true);

    public void addInt() {
        lock.lock();
        try {
            a++;
            System.out.println(Thread.currentThread().getName() + " ___________ " + a + " _ " + lock.getHoldCount());
        } finally {
            lock.unlock(); // 必須在 finally 中釋放,防止異常導致死鎖
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final MulThreadTest test = new MulThreadTest(); // 單例共享
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) test.addInt();
            }, "thread" + i).start();
        }
        // 建議加 join 等待所有線程結束再檢查結果
        Thread.sleep(1000);
        System.out.println("最終結果 a = " + a); // 應穩定輸出 300
    }
}

關鍵最佳實踐總結

錯誤做法正確做法說明
線程內 new 實例外部構建單例傳入保證鎖與共享變量同生命周期
lock 非 staticlock 聲明 static final類級別共享同一把鎖
無 finally 釋放鎖try-finally 包裹 unlock異常安全,避免死鎖
main 不等待線程join 或 CountDownLatch確保主線程看到最終結果

延伸思考

  • ReentrantLock(true) 開啟公平模式,吞吐略低但避免飢餓;高併發非公平模式性能更好。
  • Java 8+ 可用 LongAdder 或 AtomicLong 替代鎖+計數器,無鎖 CAS 性能更優。
  • 若業務允許,synchronized 配合 JIT 優化(偏向鎖、輕量級鎖)往往比顯式 Lock 更簡潔高效。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved