多執行緒環境下,共享資源的並發存取若缺乏同步,必然導致資料競爭、可見性失效、指令重排等問題。Java 提供 synchronized 關鍵字作為內建同步原語,經歷 JDK 1.6 鎖優化(偏向鎖、輕量級鎖、重量級鎖)後,效能已大幅提升。本文系統剖析同步原理、鎖類型、代碼塊與方法差異、常見陷阱與現代替代方案。
synchronized 作用於:實例方法(鎖 this)、靜態方法(鎖 Class 物件)、同步代碼塊(鎖指定物件)。| 形式 | 鎖對象 | 粒度 | 適用場景 |
|---|---|---|---|
synchronized void method() | 當前實例 (this) | 粗(整個方法) | 簡單業務、方法邏輯緊湊 |
static synchronized void method() | 當前類 Class 物件 | 粗(類級別) | 靜態資源初始化、單例雙重檢查 |
synchronized(obj) { ... } | 指定物件 obj | 細(僅代碼塊) | 高併發熱點、非 this 鎖、條件等待 |
public class BankAccount {
private final Object balanceLock = new Object(); // 專用鎖,封裝性佳
private long balance;
public void deposit(long amount) {
synchronized (balanceLock) { // 縮小同步範圍
balance += amount;
}
}
public void transfer(BankAccount target, long amount) {
// 避免死鎖:固定獲取鎖順序(hashCode 排序)
Object first = this.balanceLock;
Object second = target.balanceLock;
if (System.identityHashCode(first) > System.identityHashCode(second)) {
first = target.balanceLock;
second = this.balanceLock;
}
synchronized (first) {
synchronized (second) {
this.balance -= amount;
target.balance += amount;
}
}
}
}
鎖只能升級不能降級,但 JVM 會在安全點嘗試撤銷偏向鎖、膨脹輕量級鎖。
synchronized (list) { list = new ArrayList(); } 鎖失效,應鎖 final 物件。synchronized("lock"),常量池共享導致非預期阻塞。volatile 禁止指令重排,否則可能返回半初始化物件。| 方案 | 優點 | 缺點 | 適用 |
|---|---|---|---|
ReentrantLock | 可中斷、公平鎖、條件變數、嘗試獲取 | 需手動 unlock、語法較繁 | 複雜同步邏輯、需超時控制 |
ReadWriteLock | 讀讀並行、寫獨佔 | 寫鎖饑餓風險 | 讀多寫少(緩存、配置) |
StampedLock | 樂觀讀、無寫時無鎖 | 不支援條件變數、不可重入 | 高讀併發、可容忍臨時不一致 |
Atomic* | 無鎖 CAS、效能極高 | 僅支援單變數操作 | 計數器、旗標、累加器 |
ConcurrentHashMap | 分段鎖/CAS、高併發 Map | size() 弱一致 | 共享快取、註冊表 |
public class BoundedBuffer<T> {
private final T[] buffer;
private int head, tail, count;
private final Object lock = new Object();
@SuppressWarnings("unchecked")
public BoundedBuffer(int capacity) {
buffer = (T[]) new Object[capacity];
}
public void put(T item) throws InterruptedException {
synchronized (lock) {
while (count == buffer.length) lock.wait(); // 滿等待
buffer[tail] = item;
tail = (tail + 1) % buffer.length;
count++;
lock.notifyAll();
}
}
public T take() throws InterruptedException {
synchronized (lock) {
while (count == 0) lock.wait(); // 空等待
T item = buffer[head];
buffer[head] = null;
head = (head + 1) % buffer.length;
count--;
lock.notifyAll();
return item;
}
}
}
synchronized 經過多版本優化,仍是最通用、可讀性最高的同步手段。理解鎖升級、避免常見陷阱、善用 java.util.concurrent 高階工具,是寫出高效能、無 Bug 並發程式的關鍵。記住:**同步範圍越小越好,鎖物件越私有越好,業務邏輯越簡單越好。**