在 Java 5 之前,synchronized 是實現線程同步的核心機制。雖然後來引入了 Lock 接口,但 synchronized 依然是最基礎、最常用的同步手段。本文通過豐富的代碼示例,深入剖析 synchronized 修飾方法與代碼塊的行為差異,幫助開發者精準掌握鎖的粒度與作用範圍。
synchronized 的關鍵在於「鎖住什麼對象」:
this)Class 對象synchronized(this) 或 synchronized(MyClass.class))同一把鎖同一時間只允許一個線程進入臨界區,其他線程進入等待池。JVM 通過內存屏障保證:進入同步塊時從主內存讀取數據,退出前將修改刷回主內存,確保可見性。
public class SynchronizedTest1 extends Thread {
private synchronized void testSynchronizedMethod() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " testSynchronizedMethod:" + i);
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
@Override public void run() { testSynchronizedMethod(); }
public static void main(String[] args) {
SynchronizedTest1 t = new SynchronizedTest1();
t.start(); // 子線程
t.testSynchronizedMethod(); // 主線程
}
}
輸出:主線程先完整執行 0~9,子線程才開始執行。因為兩者共用同一實例鎖。
public static void main(String[] args) {
Thread t = new SynchronizedTest1(); t.start();
Thread t1 = new SynchronizedTest1(); t1.start();
}
輸出:兩線程交替執行,同步失效。原因:兩個 SynchronizedTest1 實例擁有各自的鎖。
private static synchronized void testSynchronizedMethod() { ... }
改為靜態方法後,鎖升級為類鎖(SynchronizedTest1.class),兩線程重新同步執行。
this 鎖住實例private void testSynchronizedBlock() {
synchronized (this) { ... }
}
Class 對象實現跨實例同步private void testSynchronizedBlock() {
synchronized (SynchronizedTest2.class) { ... }
}
此時即使創建多個實例,線程間也能同步,因為共享同一把類鎖。
| 場景 | 表現 | 原因 |
|---|---|---|
| 兩線程訪問同一對象的同步方法 | 串行執行 | 共享實例鎖 |
| 兩線程訪問不同對象的同步方法 | 並發執行 | 各自持有不同鎖 |
| 訪問同步靜態方法 | 全局串行 | 共享 Class 鎖 |
| 一線程訪問同步方法,另一線程訪問非同步方法 | 並發執行 | 非同步方法不獲取鎖 |
| 同一對象的不同同步方法 | 串行執行 | 共享同一把實例鎖 |
String、包裝類等不可變對象作為鎖(常量池可能導致意外共享)。ReentrantLock、StampedLock 或無鎖數據結構。synchronized 是 Java 併發編程的基石。理解「誰持有鎖、鎖住什麼、保護哪段代碼」是寫出正確高效同步代碼的前提。通過本文的實例對比,相信讀者能靈活運用方法鎖、代碼塊鎖、實例鎖與類鎖,應對各種併發場景。