Spring Boot高并发 锁的使用方法

Spring Boot高并发 锁的使用方法


在高并发场景中(比如电商秒杀、抢票系统、转账交易),多个线程/用户会同时操作同一共享资源(如库存、账户余额、订单号)。如果不做控制,会导致数据错误(如库存超卖、余额负数)、业务逻辑混乱(如重复下单)。锁(Lock)是解决这类问题的核心工具之一。

一、概述:为什么高并发下需要锁?

1. 高并发的“数据竞争”问题

当多个线程同时修改同一个共享资源时(如数据库的库存字段、内存中的缓存值),如果没有控制,会出现“数据不一致”。例如:

  • 电商场景:商品库存剩余10件,用户A和用户B同时下单,两个线程同时读取到库存为10,都扣减1后写回9,最终库存变成9(实际应卖出2件,库存应为8)。
  • 转账场景:用户账户余额100元,同时发起两笔50元转账,两个线程都读到余额100,都扣减50后写回50,最终余额变成50(实际应扣减100,余额0)。
2. 锁的核心作用

锁是一种“互斥机制”,保证同一时刻只有一个线程能操作共享资源,避免数据竞争。类比现实中的“公共卫生间”:锁门后,其他人必须等待,直到当前用户释放锁(开门)。

二、锁的类型与适用场景

在Spring Boot中,常用的锁分为3类,需根据业务场景选择:

锁类型实现方式适用场景优点缺点
JVM内置锁synchronized关键字单体应用(单进程)的小范围并发代码简单,JVM自动管理锁无法跨进程(分布式场景无效)
JUC显式锁ReentrantLock(Lock接口)单体应用需要灵活控制锁(如超时、可中断)支持超时、可中断、公平锁需要手动释放锁(否则死锁)
分布式锁Redis(Redisson)、ZooKeeper分布式系统(多进程/多服务器)的并发跨进程协调,全局唯一依赖外部组件(如Redis),有性能开销

三、锁的具体使用与代码实现

场景说明:模拟“电商库存扣减”

需求:用户下单时扣减商品库存,要求高并发下库存不能超卖(库存≥0)。
假设商品ID为1001,初始库存10件。

1. JVM内置锁:synchronized

适用于单体应用(只有1个Spring Boot实例),代码简单,JVM自动加锁/释放。

@Service
public class StockService {// 模拟数据库中的库存(实际开发中用数据库或缓存)private int stock = 10;// 下单扣减库存(synchronized保证同一时刻只有1个线程执行)public synchronized boolean deductStock(int productId, int count) {// 检查库存是否足够if (stock < count) {return false; // 库存不足}// 模拟业务耗时(如查询数据库、记录日志)try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 扣减库存stock -= count;System.out.println("扣减成功,剩余库存:" + stock);return true;}
}
关键说明
  • synchronized修饰方法时,锁的是当前对象(this);若修饰静态方法,锁的是类(StockService.class)。
  • 缺点:无法跨进程(如果部署多个Spring Boot实例,每个实例的stock是独立的,锁无效)。
2. JUC显式锁:ReentrantLock

适用于单体应用,但需要更灵活的锁控制(如设置超时、可中断)。

@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); // 模拟业务耗时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();}}
}
关键说明
  • tryLock(timeout, unit):尝试加锁,超时未获取则放弃(避免线程无限等待)。
  • finally中释放锁:必须手动释放,否则其他线程永远无法获取锁(死锁)。
  • 优点:比synchronized灵活(支持超时、可中断),适合复杂业务逻辑。
3. 分布式锁:Redisson(基于Redis)

适用于分布式系统(多个Spring Boot实例部署),解决跨进程的并发问题。

@Service
public class StockService {@Autowiredprivate RedissonClient redissonClient;// 模拟数据库库存(实际用数据库或缓存,如Redis存储库存)private int stock = 10;public boolean deductStock(int productId, int count) {// 定义锁的名称(按商品ID隔离,不同商品用不同锁)String lockKey = "lock:product:" + productId;RLock lock = redissonClient.getLock(lockKey);try {// 加锁(自动续期,防止业务耗时过长锁过期)// 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); // 模拟业务耗时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();}}}
}
关键说明
  • 锁名称:用lock:product:1001隔离不同商品,避免不同商品的库存操作互相阻塞。
  • 自动续期:Redisson默认会为锁“续期”(每10秒续30秒),防止业务逻辑未执行完锁就过期(比如扣库存需要20秒,锁30秒过期,续期避免提前释放)。
  • 分布式场景有效性:多个Spring Boot实例通过Redis的lockKey协调,同一时刻只有1个实例能获取锁,避免跨进程的库存超卖。

四、实际业务举例:电商秒杀场景

场景描述

某商品开启秒杀(库存100件),1000个用户同时点击“立即购买”,需要保证:

  • 只有前100个用户能成功购买(库存不超卖)。
  • 后续用户提示“已售罄”。
解决方案(分布式锁)
  1. 用户点击下单时,先通过Redisson获取该商品的分布式锁(lock:seckill:productId)。
  2. 获得锁的线程检查库存是否足够(stock > 0)。
  3. 库存足够则扣减库存,生成订单;否则返回“已售罄”。
  4. 释放锁,让其他线程继续竞争。
关键点
  • 锁粒度:按商品ID加锁(如lock:seckill:1001),不同商品的秒杀互不影响,提升并发效率。
  • 防死锁:设置锁的自动释放时间(如30秒),即使业务异常未释放锁,锁也会自动过期。
  • 性能优化:库存可存储在Redis中(GET/SET操作比数据库快),减少数据库压力。

五、总结

1. 锁的选择原则
  • 单体应用:优先用synchronized(简单)或ReentrantLock(需要灵活控制)。
  • 分布式系统:必须用分布式锁(如Redisson),避免跨进程数据竞争。
2. 注意事项
  • 锁粒度:尽量缩小锁的范围(只锁共享资源的操作代码),避免“锁整个方法”降低性能。
  • 防死锁:设置锁的超时时间(如tryLock(5, 30, TimeUnit.SECONDS)),避免线程无限等待。
  • 性能权衡:锁会降低并发吞吐量(同一时刻只有1个线程操作),需结合业务场景(如秒杀允许少量延迟)。
3. 扩展思考
  • 无锁方案:对于简单计数(如访问量),可用AtomicInteger(基于CAS无锁操作),但无法解决复杂业务逻辑(如库存扣减+订单生成)。
  • 读写锁:读多写少场景(如商品详情页缓存),可用ReentrantReadWriteLock(允许多个读锁并发,写锁互斥)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86923.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86923.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二十九:Dynamic Prompts插件动态提示词讲解

引言:可变化提示词,随机抽取不固定 使用方式一:{提示词1|提示词2|。。。。}------从提示词种随机抽取生成 方式二:{25::提示词1|75::提示词2}------数字为每个提示词的占比,相当于权重 方式三:{2$$提示词1|提示词2|提示词3|提示词4|。。。}从中选区2个搭配生成(可以换 比…

vscode 改注释的颜色,默认是灰色的,想改成红色

修改VScode编辑器默认注释的颜色_databricks代码中怎么设置让注释是灰色的-CSDN博客 //改变注释颜色"editor.tokenColorCustomizations": {"comments": "#009933" // 注释}, //如果后面还加内容&#xff0c;记得块末用逗号隔开我自己用的vscdoe.…

chili3d笔记22 正交投影3d重建笔记3 面构建

双视图重建3d solid import { FaceNode } from "chili"; import {IDocument,IEdge,Logger,ShapeNode,XYZ } from "chili-core"; import { Graph } from "graphlib"; function pointToString(point: XYZ): string {return ${point.x.toFixed(0)}-…

Kotlin 协程使用与通信

一、协程基础使用 1. 协程的三种创建方式 (1) launch - 启动后台作业 val job CoroutineScope(Dispatchers.IO).launch {// 后台操作delay(1000)println("任务完成 ${Thread.currentThread().name}")// 输出&#xff1a;任务完成 DefaultDispatcher-worker-1 } j…

Ubuntu服务器(公网)- Ubuntu客户端(内网)的FRP内网穿透配置教程

以下是为Ubuntu服务器&#xff08;公网&#xff09;- Ubuntu客户端&#xff08;内网&#xff09;的FRP内网穿透配置教程&#xff0c;基于最新版本&#xff08;2025年6月&#xff0c;使用frp_0.61.1_linux_amd64&#xff09;整理&#xff1a; 一、服务端配置&#xff08;公网Ubu…

什么是哈希函数(SHA-256)

SHA-256 是区块链系统中最核心的加密基础之一&#xff0c;尤其是在比特币、以太坊、文件存证等场景中扮演“指纹识别器”的角色。下面是对它的详细讲解&#xff0c;包括原理、特点、用途和代码示例。 &#x1f4cc; 一、什么是 SHA-256&#xff1f; SHA-256 是一种密码学哈希函…

大模型的“Tomcat”:一文读懂AI推理引擎(Inference Engine)

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 免责声明~ 任何文章不要过度深思&#xff01; 万事万物都经不起审视&#xff0c;因为世上没有同样的成长环境&#xff0c;也没有同样的认知水平&#xff0c;更「没有适用于所有人的解决方案…

《从0到1:C/C++音视频开发自学完全指南》

从0到1&#xff1a;C/C音视频开发自学完全指南 一、开篇&#xff1a;为什么选择C/C切入音视频开发&#xff1f; 当你刷着抖音短视频、参加腾讯会议、观看B站直播时&#xff0c;背后都是音视频技术在支撑。根据艾瑞咨询数据&#xff0c;2024年中国音视频相关产业规模已突破5000…

微信小程序之单行溢出隐藏和双行溢出隐藏

首先&#xff0c;我们做个text&#xff0c;加入了一个长文本&#xff0c;就像下面那样&#xff1a; wxml : <view class"container"><text>刘德华&#xff08;Andy Lau&#xff09;&#xff0c;1961年9月27日出生于中国香港&#xff0c;华语影视男演员、…

PHP安装使用教程

一、PHP 简介 PHP&#xff08;Hypertext Preprocessor&#xff09;是一种广泛应用的开源服务器端脚本语言&#xff0c;尤其适用于 Web 开发&#xff0c;可嵌入 HTML 中使用。其运行速度快、易学易用&#xff0c;支持多种数据库和平台。 二、PHP 安装教程 2.1 支持平台 PHP 支…

ThreadLocal、InheritableThreadLocal与TransmittableThreadLocal深度解析

文章目录 一、概念说明1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 二、使用场景1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 三、存在的问题1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 四、示例…

ERP系统Bug记录

2025.06.30 2025/06/30-10:51:02 [http-nio-9999-exec-3] com.yxx.jsh.erp.service.LogService - 异常码[300],异常提示[数据查询异常],异常[{}] java.lang.NullPointerException: nullat com.yxx.jsh.erp.base.TableSupport.getBuildPageRequest(TableSupport.java:46)at com…

C# Avalonia 的 Source Generators 用处

C# Avalonia 的 Source Generators 用处 文章目录 **1. 自动生成 MVVM 绑定代码****2. 强类型 XAML 数据绑定****3. 自动注册视图&#xff08;View&#xff09;与视图模型&#xff08;ViewModel&#xff09;****4. 资源文件与本地化的强类型访问****5. 路由事件与命令的自动化处…

stm32之测量占空比

#include "tim4.h"void TIM4_Init(void) {// 开启时钟RCC->APB1ENR | RCC_APB1ENR_TIM4EN;RCC->APB2ENR | RCC_APB2ENR_IOPBEN; // 使用 TIM4 的 GPIOB 时钟// 配置 PB6 为浮空输入 CNF 01 MODE 00GPIOB->CRL & ~GPIO_CRL_MODE6;GPIOB->CRL & ~G…

工厂模式 - Flutter中的UI组件工厂,按需生产各种“产品

想要动态创建不同风格的按钮&#xff1f;想一键切换整个主题&#xff1f;工厂模式就是你的"生产流水线"&#xff01; 想象一下这个场景&#xff1a; 你决定扩大奶茶店业务&#xff0c;推出两个品牌系列&#xff1a; 经典系列&#xff1a;传统珍珠奶茶&#xff0c;红…

基于 SpringBoot+Vue.js+ElementUI 的 Cosplay 论坛设计与实现7000字论文

基于 SpringBootVue.jsElementUI 的 Cosplay 论坛设计与实现 摘要 本论文设计并实现了一个基于 SpringBoot、Vue.js 和 ElementUI 的 Cosplay 论坛平台。该平台旨在为 Cosplay 爱好者提供一个集作品展示、交流互动、活动组织于一体的综合性社区。论文首先分析了 Cosplay 论坛…

超标量处理器11-Alpha21264 处理器

1. 简介 21264处理器是一款4-way&#xff0c;乱序执行的超标量处理器&#xff0c;采用0.35um的CMOS工艺&#xff0c;工作电压是2.2V, 工作频率是466-667MHz; 处理器能支持60条指令&#xff0c;也即ROB的深度是60; Load/Store指令也采取乱序执行, 总共7级流水。I-CACHE和D-CACH…

Spring 中 Bean 的生命周期

一、什么是 Bean 生命周期&#xff1f; Spring 中的 Bean 生命周期是指一个 Bean 从 被容器创建到 最终销毁 所经历的一系列过程。 它体现了 Spring IOC 容器在管理 Bean 实例时所执行的各个钩子流程&#xff0c;包括初始化、依赖注入、增强处理、销毁等多个环节。 二、Bean 生…

C++ 中 std::string 与 QString 的深度剖析

在 C 编程领域&#xff0c;std::string 和 QString 是两种广泛应用的字符串类型&#xff0c;它们在设计理念、功能特性以及适用场景上都呈现出鲜明的特点。本文将从多个维度对这两种字符串类型进行深度剖析&#xff0c;并详细阐述它们之间的相互转化方法。 std::string 是 C 标…

不止于“修补”:我如何用Adobe AI重塑设计与视频叙事流程

最近我深度体验了一把来自英国Parvis School of Economics and Music的Adobe正版教育订阅&#xff0c;在把玩PhotoShop、Premiere Pro这些“老伙计”的新功能时&#xff0c;的确挖到了一些宝藏&#xff0c;觉得非常有必要与大家说道说道。首先得聊聊这个订阅给我的直观感受&…