面試官讓我解釋樂觀鎖和悲觀鎖,我用這個方法輕松拿下 Offer!
引言
朋友們,今天我們來聊聊 Java 面試中一個經常被問到的高頻問題——樂觀鎖和悲觀鎖!這不僅是社招面試的重點,也是工作中優化并發性能的必備知識。
作為一個經歷過 N 場面試、踩過無數坑、在面試官手里九死一生的“戰損程序員”,我可以很負責任地告訴你:
面試官喜歡問這個問題!
所以,今天我就用故事+代碼+實戰的方式,把這個問題講透!看完這篇文章,你的面試表現必定技高一籌!
故事背景:搶票大戰
假設你有一個搶票系統,用戶在高峰期瘋狂點擊“搶票”按鈕,每張票都炙手可熱。
- 樂觀鎖 的思路是:“我覺得你不會搶走這張票,我先買著,等我支付時再檢查票是否被搶光。”
- 悲觀鎖 的思路是:“誰也別搶,我先鎖住這張票,等我支付完再放開。”
我們用更形象的比喻來理解:
圖片
那么,兩者的具體實現方式又是什么呢?繼續往下看!
悲觀鎖的實現方式
1. Synchronized & ReentrantLock
悲觀鎖的核心思想是獨占式訪問,即當前線程訪問資源時,會阻塞其他線程的訪問。最典型的實現方式是 Synchronized 和 ReentrantLock。
(1)Synchronized
圖片
特點:
- JVM 內置鎖,使用方便。
- 適用于簡單場景,如同步方法或同步代碼塊。
(2)ReentrantLock
圖片
特點:
- 顯示加鎖、解鎖,比 Synchronized 更靈活。
- 可實現公平鎖(避免某些線程一直得不到鎖)。
2. 數據庫悲觀鎖(for update)
如果你的搶票邏輯是基于數據庫的,我們可以使用 悲觀鎖 來防止超賣:
圖片
特點:
- 鎖住行數據,其他事務無法修改。
- 適用于高并發場景,但可能導致鎖競爭,影響性能。
樂觀鎖的實現方式
1. CAS(Compare and Swap)
CAS 是樂觀鎖的核心機制,它的思路是先讀取數據,更新時檢查數據是否被改動,如果沒有變化就更新,否則重試。
(1)Java Atomic 類
圖片
特點:
- 無鎖機制,高并發下性能更好。
- 適用于計數、累加等輕量級操作。
2. 數據庫樂觀鎖(版本號機制)
在數據庫中,我們可以用版本號來實現樂觀鎖。
(1)數據庫表設計
圖片
(2)更新邏輯
圖片
特點:
- 只有當 version 沒變,才會成功更新,否則說明數據被別人改了,需要重試。
(3)Java 代碼實現
圖片
樂觀鎖 vs 悲觀鎖:如何選擇?
圖片
總結:
- 讀多寫少,優先用 樂觀鎖(減少阻塞)。
- 讀寫頻繁,考慮 悲觀鎖(避免沖突)。
- 高并發環境,推薦 CAS 或版本號機制。
- 數據庫更新,根據實際情況選擇 for update(悲觀鎖)或 version(樂觀鎖)。
面試官可能的追問點
1、CAS 有哪些缺點?
- ABA 問題(解決方案:AtomicStampedReference)
- 自旋失敗(高并發下可能導致 CPU 消耗大)
2、如何優化數據庫悲觀鎖?
- 限制鎖定的范圍(只鎖定必要字段)
- 使用合適的事務隔離級別(避免死鎖)
3、在分布式環境下如何實現樂觀鎖?
- Redis 分布式鎖(如 Redisson)
- Zookeeper 臨時節點