多台 Tomcat 叢集部署同一應用,某業務方法使用 `synchronized` 鎖定關鍵區段(如扣減庫存、生成單號)。開發者疑慮:synchronized 能否跨 JVM 實現互斥?
synchronized 僅在單一 JVM 進程內有效,無法跨多台 Tomcat(多進程)實現互斥。
| 層面 | 說明 | |||
|---|---|---|---|---|
| 實現機制 | JVM 監視器鎖,基於物件頭 Mark Word,依賴共享堆記憶體中的物件頭狀態。 | |||
| 記憶體範圍 | 每個 Tomcat 進程擁有獨立 JVM、獨立堆、獨立物件實例。物件頭鎖狀態不共享。 | |||
| 結果 | Tomcat A 鎖住 `this`,Tomcat B 看到的是另一個 `this` 實例,鎖狀態互不干擾,並發進入臨界區 → 數據不一致。 |
| 方案 | 原理 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 資料庫悲觀鎖 | `SELECT ... FOR UPDATE` / `UPDATE ... WHERE version = ?` | 無額外元件、強一致性、事務原生支援 | 效能較低、連接池壓力大、死鎖風險 | 低併發、強一致性要求高(訂單、扣款) |
| 資料庫樂觀鎖 | 版本號/時間戳 `UPDATE ... SET val=?, ver=ver+1 WHERE id=? AND ver=?` | 無鎖開銷、高併發友好 | 衝突重試邏輯複雜、不適用長事務 | 高併發、衝突率低(庫存、點贊) |
| Redis SETNX/Redisson | `SET lock_key NX EX 30` + Lua 腳本續期/釋放 | 高效能、支援可重入、公平鎖、紅鎖多節點 | 需維護 Redis 叢集、網路分區風險 | 高併發、微服務、分佈式任務調度 |
| ZooKeeper 臨時有序節點 | 建立臨時順序節點,最小序號獲取鎖,Watch 前驅節點 | 強一致性、天然支援會話超時釋放、羊群效應可控 | 寫效能受限、運維複雜、延遲較高 | 配置管理、Leader 選舉、強一致協調 |
| 中央化協調服務 | 獨立 Lock Service(gRPC/HTTP),內部用 ConcurrentHashMap + 條件變數 | 邏輯集中、易擴展業務語義(鎖降級、超時回調) | 單點風險(需高可用)、額外網路跳轉 | 內部中台、業務強相關鎖 |
<dependency>
<groupId>org.redisson</groupId>
<artifactId>:redisson-spring-boot-starter</artifactId>
<version>3.23.0</version>
</dependency>
// 使用
@Autowired RedissonClient redisson;
public void deductStock(String productId) {
RLock lock = redisson.getLock("stock:" + productId);
boolean acquired = lock.tryLock(5, 10, TimeUnit.SECONDS); // 等待5s,租約10s自動續期
if (!acquired) throw new RuntimeException("獲取鎖超時");
try {
// 臨界區:查庫存、扣減、寫入訂單
stockService.deduct(productId);
} finally {
lock.unlock();
}
}
synchronized 是進程內鎖,分佈式環境必須引入外部共享存儲(Redis/DB/ZK)實現跨進程互斥。選型原則:業務允許最終一致 → Redis;強一致/低併發 → DB 悲觀/樂觀鎖;協調/元數據 → ZK。並以資料庫約束作為最後兜底,構建多層防禦體系。