Java 多線程環境下,線程安全問題僅源於共享可變狀態:
public class Test implements Runnable {
private int j; // 實例變量,共享狀態
public void testThreadLocal() {
System.out.println(Thread.currentThread().getId() + ":============================= begin");
j = 2;
System.out.println(Thread.currentThread().getId() + ":" + j);
j = 20;
System.out.println(":" + j * 3 + ":"); // 預期 60
System.out.println(Thread.currentThread().getId() + ":============================= end");
}
public static void main(String[] args) {
Test t = new Test();
for (int i = 0; i < 3000; i++) {
new Thread(t).start(); // 共享同一 Test 實例
}
}
@Override public void run() { testThreadLocal(); }
}
現象:輸出出現 :6: 等錯誤值(預期 :60:),證明多線程交織修改 j 導致數據競爭。
// main 中 new Thread(new Test()).start() 創建多個實例
public synchronized void testThreadLocal() { ... } // 鎖 this
無效:每個線程鎖的是不同的 this,互不阻塞。
// main 中共享同一 Test 實例
public synchronized void testThreadLocal() { ... } // 鎖同一 this,有效
public void testThreadLocal() {
synchronized (this.getClass()) { // 或 synchronized (Test.class)
// 臨界區代碼
}
}
原理:Test.class 是 JVM 加載類時創建的唯一 Class 對象,全局唯一,所有實例共享此鎖。
private static int j; // 類變量
public synchronized static void testThreadLocal() { ... }
// 等價於
public static void testThreadLocal() {
synchronized (Test.class) { ... }
}
靜態方法無 this,synchronized 直接鎖 Class 對象,天然適配類變量保護。
JVM 規範允許 64 位 long/double 非原子寫入(分兩次 32 位操作)。多線程並發寫入可能導致「撕裂讀/寫」(高低 32 位來自不同線程)。
volatile 或用 AtomicLong/AtomicReference,或同步塊保護。| 場景 | 推薦鎖策略 |
|---|---|
| 保護實例變量,單實例共享 | synchronized 實例方法 / synchronized(this) |
| 保護實例變量,多實例並發 | synchronized(ClassName.class) 類鎖 |
| 保護類變量 (static) | synchronized static 方法 / synchronized(ClassName.class) |
| 高併發、細粒度控制 | ReentrantLock / ReadWriteLock / StampedLock |
| 簡單計數/狀態 | AtomicInteger / LongAdder / ThreadLocal |