使用 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 非 static | lock 聲明 static final | 類級別共享同一把鎖 |
| 無 finally 釋放鎖 | try-finally 包裹 unlock | 異常安全,避免死鎖 |
| main 不等待線程 | join 或 CountDownLatch | 確保主線程看到最終結果 |
ReentrantLock(true) 開啟公平模式,吞吐略低但避免飢餓;高併發非公平模式性能更好。LongAdder 或 AtomicLong 替代鎖+計數器,無鎖 CAS 性能更優。synchronized 配合 JIT 優化(偏向鎖、輕量級鎖)往往比顯式 Lock 更簡潔高效。