詳解解Spring Boot高并發(fā)鎖的使用方法
在高并發(fā)場景中(比如電商秒殺、搶票系統(tǒng)、轉(zhuǎn)賬交易),多個線程/用戶會同時操作同一共享資源(如庫存、賬戶余額、訂單號)。如果不做控制,會導致數(shù)據(jù)錯誤(如庫存超賣、余額負數(shù))、業(yè)務(wù)邏輯混亂(如重復下單)。鎖(Lock)是解決這類問題的核心工具之一。
一、概述:為什么高并發(fā)下需要鎖?
1. 高并發(fā)的“數(shù)據(jù)競爭”問題
當多個線程同時修改同一個共享資源時(如數(shù)據(jù)庫的庫存字段、內(nèi)存中的緩存值),如果沒有控制,會出現(xiàn)“數(shù)據(jù)不一致”。例如:
- 電商場景:商品庫存剩余10件,用戶A和用戶B同時下單,兩個線程同時讀取到庫存為10,都扣減1后寫回9,最終庫存變成9(實際應(yīng)賣出2件,庫存應(yīng)為8)。
- 轉(zhuǎn)賬場景:用戶賬戶余額100元,同時發(fā)起兩筆50元轉(zhuǎn)賬,兩個線程都讀到余額100,都扣減50后寫回50,最終余額變成50(實際應(yīng)扣減100,余額0)。
2. 鎖的核心作用
鎖是一種“互斥機制”,保證同一時刻只有一個線程能操作共享資源,避免數(shù)據(jù)競爭。類比現(xiàn)實中的“公共衛(wèi)生間”:鎖門后,其他人必須等待,直到當前用戶釋放鎖(開門)。
二、鎖的類型與適用場景
在Spring Boot中,常用的鎖分為3類,需根據(jù)業(yè)務(wù)場景選擇:
| 鎖類型 | 實現(xiàn)方式 | 適用場景 | 優(yōu)點 | 缺點 |
|---|---|---|---|---|
| JVM內(nèi)置鎖 | synchronized關(guān)鍵字 | 單體應(yīng)用(單進程)的小范圍并發(fā) | 代碼簡單,JVM自動管理鎖 | 無法跨進程(分布式場景無效) |
| JUC顯式鎖 | ReentrantLock(Lock接口) | 單體應(yīng)用需要靈活控制鎖(如超時、可中斷) | 支持超時、可中斷、公平鎖 | 需要手動釋放鎖(否則死鎖) |
| 分布式鎖 | Redis(Redisson)、ZooKeeper | 分布式系統(tǒng)(多進程/多服務(wù)器)的并發(fā) | 跨進程協(xié)調(diào),全局唯一 | 依賴外部組件(如Redis),有性能開銷 |
三、鎖的具體使用與代碼實現(xiàn)
場景說明:模擬“電商庫存扣減”
需求:用戶下單時扣減商品庫存,要求高并發(fā)下庫存不能超賣(庫存≥0)。
假設(shè)商品ID為1001,初始庫存10件。
1. JVM內(nèi)置鎖:synchronized
適用于單體應(yīng)用(只有1個Spring Boot實例),代碼簡單,JVM自動加鎖/釋放。
@Service
public class StockService {
// 模擬數(shù)據(jù)庫中的庫存(實際開發(fā)中用數(shù)據(jù)庫或緩存)
private int stock = 10;
// 下單扣減庫存(synchronized保證同一時刻只有1個線程執(zhí)行)
public synchronized boolean deductStock(int productId, int count) {
// 檢查庫存是否足夠
if (stock < count) {
return false; // 庫存不足
}
// 模擬業(yè)務(wù)耗時(如查詢數(shù)據(jù)庫、記錄日志)
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 扣減庫存
stock -= count;
System.out.println("扣減成功,剩余庫存:" + stock);
return true;
}
}
關(guān)鍵說明
synchronized修飾方法時,鎖的是當前對象(this);若修飾靜態(tài)方法,鎖的是類(StockService.class)。- 缺點:無法跨進程(如果部署多個Spring Boot實例,每個實例的
stock是獨立的,鎖無效)。
2. JUC顯式鎖:ReentrantLock
適用于單體應(yīng)用,但需要更靈活的鎖控制(如設(shè)置超時、可中斷)。
@Service
public class StockService {
private int stock = 10;
// 顯式鎖(可重入鎖,支持公平/非公平)
private final ReentrantLock lock = new ReentrantLock();
public boolean deductStock(int productId, int count) {
// 嘗試加鎖(最多等待2秒,避免死鎖)
try {
if (lock.tryLock(2, TimeUnit.SECONDS)) {
if (stock >= count) {
Thread.sleep(100); // 模擬業(yè)務(wù)耗時
stock -= count;
System.out.println("扣減成功,剩余庫存:" + stock);
return true;
} else {
System.out.println("庫存不足");
return false;
}
} else {
System.out.println("獲取鎖超時");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
// 必須在finally中釋放鎖(避免異常導致鎖未釋放)
lock.unlock();
}
}
}
關(guān)鍵說明
tryLock(timeout, unit):嘗試加鎖,超時未獲取則放棄(避免線程無限等待)。finally中釋放鎖:必須手動釋放,否則其他線程永遠無法獲取鎖(死鎖)。- 優(yōu)點:比
synchronized靈活(支持超時、可中斷),適合復雜業(yè)務(wù)邏輯。
3. 分布式鎖:Redisson(基于Redis)
適用于分布式系統(tǒng)(多個Spring Boot實例部署),解決跨進程的并發(fā)問題。
@Service
public class StockService {
@Autowired
private RedissonClient redissonClient;
// 模擬數(shù)據(jù)庫庫存(實際用數(shù)據(jù)庫或緩存,如Redis存儲庫存)
private int stock = 10;
public boolean deductStock(int productId, int count) {
// 定義鎖的名稱(按商品ID隔離,不同商品用不同鎖)
String lockKey = "lock:product:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 加鎖(自動續(xù)期,防止業(yè)務(wù)耗時過長鎖過期)
// waitTime: 等待鎖的最大時間(5秒)
// leaseTime: 鎖自動釋放時間(30秒,防止死鎖)
boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
if (!locked) {
System.out.println("獲取鎖失敗,稍后再試");
return false;
}
// 檢查并扣減庫存
if (stock >= count) {
Thread.sleep(100); // 模擬業(yè)務(wù)耗時
stock -= count;
System.out.println("扣減成功,剩余庫存:" + stock);
return true;
} else {
System.out.println("庫存不足");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
// 釋放鎖(只有自己加的鎖才能釋放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
關(guān)鍵說明
- 鎖名稱:用
lock:product:1001隔離不同商品,避免不同商品的庫存操作互相阻塞。 - 自動續(xù)期:Redisson默認會為鎖“續(xù)期”(每10秒續(xù)30秒),防止業(yè)務(wù)邏輯未執(zhí)行完鎖就過期(比如扣庫存需要20秒,鎖30秒過期,續(xù)期避免提前釋放)。
- 分布式場景有效性:多個Spring Boot實例通過Redis的
lockKey協(xié)調(diào),同一時刻只有1個實例能獲取鎖,避免跨進程的庫存超賣。
四、實際業(yè)務(wù)舉例:電商秒殺場景
場景描述
某商品開啟秒殺(庫存100件),1000個用戶同時點擊“立即購買”,需要保證:
- 只有前100個用戶能成功購買(庫存不超賣)。
- 后續(xù)用戶提示“已售罄”。
解決方案(分布式鎖)
- 用戶點擊下單時,先通過Redisson獲取該商品的分布式鎖(
lock:seckill:productId)。 - 獲得鎖的線程檢查庫存是否足夠(
stock > 0)。 - 庫存足夠則扣減庫存,生成訂單;否則返回“已售罄”。
- 釋放鎖,讓其他線程繼續(xù)競爭。
關(guān)鍵點
- 鎖粒度:按商品ID加鎖(如
lock:seckill:1001),不同商品的秒殺互不影響,提升并發(fā)效率。 - 防死鎖:設(shè)置鎖的自動釋放時間(如30秒),即使業(yè)務(wù)異常未釋放鎖,鎖也會自動過期。
- 性能優(yōu)化:庫存可存儲在Redis中(
GET/SET操作比數(shù)據(jù)庫快),減少數(shù)據(jù)庫壓力。
五、總結(jié)
1. 鎖的選擇原則
- 單體應(yīng)用:優(yōu)先用
synchronized(簡單)或ReentrantLock(需要靈活控制)。 - 分布式系統(tǒng):必須用分布式鎖(如Redisson),避免跨進程數(shù)據(jù)競爭。
2. 注意事項
- 鎖粒度:盡量縮小鎖的范圍(只鎖共享資源的操作代碼),避免“鎖整個方法”降低性能。
- 防死鎖:設(shè)置鎖的超時時間(如
tryLock(5, 30, TimeUnit.SECONDS)),避免線程無限等待。 - 性能權(quán)衡:鎖會降低并發(fā)吞吐量(同一時刻只有1個線程操作),需結(jié)合業(yè)務(wù)場景(如秒殺允許少量延遲)。
3. 擴展思考
- 無鎖方案:對于簡單計數(shù)(如訪問量),可用
AtomicInteger(基于CAS無鎖操作),但無法解決復雜業(yè)務(wù)邏輯(如庫存扣減+訂單生成)。 - 讀寫鎖:讀多寫少場景(如商品詳情頁緩存),可用
ReentrantReadWriteLock(允許多個讀鎖并發(fā),寫鎖互斥)。
到此這篇關(guān)于詳解解Spring Boot高并發(fā)鎖的使用方法的文章就介紹到這了,更多相關(guān)Spring Boot高并發(fā)鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot種如何使用?EasyExcel?實現(xiàn)自定義表頭導出并實現(xiàn)數(shù)據(jù)格式化轉(zhuǎn)換
本文詳細介紹了如何使用EasyExcel工具類實現(xiàn)自定義表頭導出,并實現(xiàn)數(shù)據(jù)格式化轉(zhuǎn)換與添加下拉框操作,通過示例和代碼,展示了如何處理不同數(shù)據(jù)結(jié)構(gòu)和注解,確保數(shù)據(jù)在導出時能夠正確顯示和格式化,此外,還介紹了如何解決特定數(shù)據(jù)類型的轉(zhuǎn)換問題,并提供了解決方案2024-11-11
SpringBoot中ApplicationEvent的使用步驟詳解
ApplicationEvent類似于MQ,是Spring提供的一種發(fā)布訂閱模式的事件處理方式,本文給大家介紹SpringBoot中ApplicationEvent的使用步驟詳解,感興趣的朋友跟隨小編一起看看吧2024-04-04

