許多開發者誤以為 synchronized 鎖住的是「代碼塊」,實則 鎖的是物件。只要持有同一把鎖 (物件),才能互斥;不同實例各有鎖,同步方法可並發執行。
class Foo extends Thread {
private int val;
public Foo(int v) { val = v; }
public synchronized void printVal(int v) { // 鎖住 this (Foo 實例)
while (true) System.out.println(v);
}
public void run() { printVal(val); }
}
public class SyncTest {
public static void main(String[] args) {
Foo f1 = new Foo(1); f1.start(); // 鎖 f1
Foo f2 = new Foo(3); f2.start(); // 鎖 f2 (不同鎖!)
}
}
輸出:1 和 3 交替輸出 → 兩線程同時執行 printVal!
若 synchronized 鎖代碼,輸出只能是全 1 或全 3。
public void printVal(int v) {
synchronized (Foo.class) { // 所有實例共享唯一 Class 物件
while (true) System.out.println(v);
}
}
Foo.class 在 JVM 中唯一,所有實例競爭同一把鎖public static synchronized void printVal(...)class Foo extends Thread {
private int val;
private static final Object LOCK = new Object(); // 專用鎖物件
public Foo(int v) { val = v; }
public void printVal(int v) {
synchronized (LOCK) { // 明確語義、不暴露類鎖
while (true) System.out.println(v);
}
}
public void run() { printVal(val); }
}
class Foo extends Thread {
private String name, val;
public Foo(String name, String v) { this.name = name; val = v; }
public void printVal() {
synchronized (val) { // 同樣字串常數池引用同一物件
while (true) System.out.println(name + val);
}
}
public void run() { printVal(); }
}
public class SyncMethodTest {
public static void main(String[] args) {
// 關鍵:傳入同一字串常數 "printVal" → 共享同一鎖物件
Foo f1 = new Foo("Foo 1:", "printVal"); f1.start();
Foo f2 = new Foo("Foo 2:", "printVal"); f2.start();
}
}
注意:字串常數池導致 "printVal" 為同一物件;若用 new String("printVal") 則為不同物件,同步失效。
| 同步寫法 | 鎖物件 | 保護範圍 | 適用場景 |
|---|---|---|---|
synchronized void method() | this (實例) | 該實例所有同步方法 | 實例狀態保護 |
static synchronized void method() | Class 物件 | 該類所有靜態同步方法 | 靜態狀態保護 |
synchronized(lock) { } | lock 物件 | 同一 lock 的所有區塊 | 精細控制、跨方法/類協調 |
synchronized(Foo.class) { } | Foo.class | 同類所有此類區塊 | 類級別協調 |
private final Object lock = new Object(); 封裝鎖,防外部干擾this、Class、字串常數、集合等可能被外部代碼鎖住導致死鎖/活鎖ReentrantLock、ReadWriteLock、ConcurrentHashMap、Atomic* 等高階工具