Spring Boot / Spring Cloud 中的 Lock 实战场景
本文聚焦 Lock(含分布式锁)在 Spring Boot / Spring Cloud 微服务开发中的真实业务场景,每个场景包含业务背景、技术背景、实现方案、代码示例和方案评价。
场景一:分布式锁 + 定时任务防重复执行
1.1 业务背景
微服务通常部署多个实例(多节点)保证高可用。Spring 的 @Scheduled 定时任务在每个实例上都会执行,导致:
问题:同一个定时任务被多个实例同时执行
实例 A ── 2:00 AM ──→ 生成日报(没问题)
实例 B ── 2:00 AM ──→ 生成日报(重复!数据重复/资源浪费)
实例 C ── 2:00 AM ──→ 生成日报(重复!)
后果:
- 报表重复生成,数据翻倍
- 批处理重复执行,产生重复数据
- 对账任务重复,触发重复扣款
- 凌晨批处理时间窗口被多个实例瓜分,反而变慢
1.2 技术背景
单机方案:使用 ReentrantLock 或 synchronized —— 但多实例下无效,因为锁只在一个 JVM 内生效。
分布式锁方案:需要一个所有实例都能访问到的公共存储来协调锁:
数据库(悲观锁 SELECT FOR UPDATE)
Redis(SET NX / RedLock)
Zookeeper(临时有序节点)
Etcd(租约机制)
其中最常用的是 Redis 分布式锁(性能高、实现简单)。
1.3 实现思路
定时任务触发
│
▼
尝试获取分布式锁(Redis SET NX + 过期时间)
│
├── 获取成功 → 执行任务 → 执行完毕 → 释放锁
│
└── 获取失败 → 跳过(其他实例已执行)
1.4 代码示例
方案 A:手动封装 Redis 分布式锁
@Component
public class RedisDistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 尝试获取锁
* @param key 锁标识
* @param expireSec 锁自动过期时间(防止死锁)
* @return 是否获取成功
*/
public boolean tryLock(String key, long expireSec) {
// SET NX + 过期时间(原子操作)
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, Thread.currentThread().getName(), expireSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
/**
* 释放锁(使用 Lua 脚本确保原子性)
*/
public void unlock(String key, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key), requestId
);
}
}
注意: 释放锁必须用 Lua 脚本验证 value(防止误删其他线程的锁)。
方案 B:使用 Redisson(推荐,自带看门狗)
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379");
return Redisson.create(config);
}
}
@Component
@Slf4j
public class ScheduledLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 在定时任务中使用分布式锁
*/
public void executeWithLock(String taskName, long waitTime, Runnable task) {
RLock lock = redissonClient.getLock("scheduled:" + taskName);
try {
// waitTime:等待锁的时间;leaseTime:锁自动释放时间(-1=看门狗自动续期)
if (lock.tryLock(waitTime, -1, TimeUnit.SECONDS)) {
log.info("获取锁成功,开始执行定时任务: {}", taskName);
try {
task.run();
} finally {
lock.unlock();
log.info("定时任务完成,释放锁: {}", taskName);
}
} else {
log.info("获取锁失败,其他实例正在执行: {}", taskName);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("定时任务被中断: {}", taskName);
}
}
}
定时任务中使用:
@Component
public class ReportScheduleTask {
@Autowired
private ScheduledLockService lockService;
// 每天凌晨 2 点执行,多实例下只有一个实例执行
@Scheduled(cron = "0 0 2 * * ?")
public void generateDailyReport() {
lockService.executeWithLock("daily-report", 10, () -> {
reportService.generateDaily();
});
}
// 每小时同步数据
@Scheduled(cron = "0 0 0/1 * * ?")
public void syncData() {
lockService.executeWithLock("data-sync", 10, () -> {
dataSyncService.sync();
});
}
}
方案 C:ShedLock 框架(开箱即用)
// 1. 添加依赖
// <dependency> <groupId>net.javacrumbs.shedlock</groupId>
// <artifactId>shedlock-spring</artifactId> </dependency>
// 2. 配置锁存储(Redis)
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "30m")
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(RedisTemplate<String, String> template) {
return new RedisLockProvider(template.getConnectionFactory());
}
}
// 3. 定时任务加 @SchedulerLock
@Component
public class ScheduledTasks {
@Scheduled(cron = "0 0 2 * * ?")
@SchedulerLock(name = "dailyReport", lockAtMostFor = "30m", lockAtLeastFor = "5m")
public void dailyReport() {
// 分布式锁自动管理,无需手动写锁逻辑
reportService.generateDaily();
}
}
1.5 优缺点
| 维度 | 说明 |
|---|---|
| ✅ 防止重复执行 | 多实例下定时任务只执行一次 |
| ✅ 故障自动恢复 | 持有锁的实例宕机,锁到期后其他实例接替 |
| ✅ Redisson 看门狗 | 自动续期,防止业务未完成锁就过期 |
| ❌ Redis 单点风险 | Redis 主从切换时可能丢失锁(RedLock 解决) |
| ❌ 增加依赖 | 需要 Redis 组件 |
| ❌ 锁过期时间难定 | 设置太短任务没完成锁就过期,太长异常恢复慢 |
场景二:ReentrantLock + 本地缓存刷新
2.1 业务背景
微服务中经常使用本地缓存(Caffeine / Guava Cache)减少对下游服务或数据库的调用:
// 本地缓存常见场景:字典数据、配置项、白名单、路由表
// 这些数据特点:读极多、写很少、允许短时间不一致
核心问题:缓存刷新时的并发控制
线程 A 读缓存 → 缓存过期 → 去数据库查询 → 回填缓存
线程 B 读缓存 → 缓存过期 → 去数据库查询 → 回填缓存
线程 C 读缓存 → 缓存过期 → 去数据库查询 → 回填缓存
问题:缓存过期瞬间,N 个请求同时穿透到数据库!
这叫「缓存雪崩」或「缓存击穿」
2.2 技术背景
需要保证:缓存刷新时,只有一个线程去加载数据,其他线程等待这个线程的结果——这就是 ReentrantLock 的典型场景。
Caffeine 内置了此机制(expireAfterWrite + 同步加载),但理解其背后的 Lock 原理对排查复杂问题很重要。
2.3 实现思路
读请求
│
▼
缓存命中?───是──→ 直接返回
│
否
│
▼
尝试获取锁(tryLock)
│
├── 成功 → 再次检查缓存(double check)
│ ├── 有其他线程已回填 → 返回
│ └── 还是没有 → 查 DB → 回填缓存 → 释放锁
│
└── 失败 → 等待(等待获取锁的线程完成)
→ 直接读缓存返回
关键点:双重检查锁(DCL)模式,和单例模式的 DCL 一个原理。
2.4 代码示例
@Component
public class LocalCacheService<K, V> {
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<K, ReentrantLock> lockMap = new ConcurrentHashMap<>();
private final CacheLoader<K, V> loader; // 数据加载器
public LocalCacheService(CacheLoader<K, V> loader) {
this.loader = loader;
}
/**
* 带锁保护的缓存读取(DCL 模式)
*/
public V get(K key) {
// ① 第一次检查:缓存命中直接返回
V value = cache.get(key);
if (value != null) {
return value;
}
// ② 获取 key 对应的锁(每个 key 一把锁,减少竞争)
ReentrantLock lock = lockMap.computeIfAbsent(key, k -> new ReentrantLock());
lock.lock();
try {
// ③ 第二次检查(DCL):可能其他线程已经加载好了
value = cache.get(key);
if (value != null) {
return value;
}
// ④ 真正加载数据(只有第一个线程到这里)
value = loader.load(key);
cache.put(key, value);
return value;
} finally {
lock.unlock();
// 清理不再使用的锁对象,防止内存泄漏
lockMap.remove(key, lock);
}
}
/**
* 强制刷新缓存
*/
public void refresh(K key) {
ReentrantLock lock = lockMap.computeIfAbsent(key, k -> new ReentrantLock());
lock.lock();
try {
V value = loader.load(key);
cache.put(key, value);
} finally {
lock.unlock();
}
}
@FunctionalInterface
public interface CacheLoader<K, V> {
V load(K key);
}
}
使用 Spring 的 Cacheable + sync 属性(更简单的方案):
@Service
public class DictService {
// Spring 4.3+ 的 sync = true 自带锁保护
@Cacheable(value = "dictCache", key = "#type", sync = true)
public List<DictItem> getDict(String type) {
// sync=true 保证只有一个线程执行此方法
// 其他线程等待结果
return dictMapper.selectByType(type);
}
}
// 同时配置 Caffeine 缓存
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10000)
.initialCapacity(100)
);
return manager;
}
}
2.5 优缺点
| 维度 | 说明 |
|---|---|
| ✅ 防止缓存击穿 | 缓存过期瞬间只有一个线程查 DB |
| ✅ 并发性能好 | 读读不互斥,只有缓存过期时才有锁竞争 |
| ✅ 双重检查 | DCL 避免不必要的重复加载 |
| ❌ 每个 key 一把锁 | key 太多时锁对象浪费内存 |
| ❌ 只在本机有效 | 多实例场景下每个实例各自加锁(不是分布式的) |
| ❌ 锁对象泄漏风险 | 如果 computeIfAbsent 后不移除,锁对象会一直堆积 |
场景三:分布式锁 + 幂等性控制(防重复提交)
3.1 业务背景
微服务中下游接口不一定是幂等的,需要防止重复请求:
业务痛点:
- 用户连续点击两次「提交订单」→ 产生两笔订单
- 支付回调重试 → 重复更新余额
- MQ 消息重复消费 → 重复插入数据
- 前端超时重试 → 服务端处理了两次
3.2 技术背景
幂等性控制常见方案:
| 方案 | 说明 | 适用场景 |
|---|---|---|
| 唯一索引 | 数据库唯一约束 | 插入类操作 |
| 状态机 | 订单状态流转校验 | 有状态的业务 |
| 分布式锁 | Redis 锁防止并发 | 高并发防重复 |
| 去重表 | 业务主键唯一表 | 支付回调、MQ 消费 |
使用分布式锁做幂等,核心思想:同一业务 ID 在同一时间只能被一个线程处理。
3.3 实现思路
请求到达(携带幂等 key,如 orderId)
│
▼
Redis SET NX key(幂等 key + 过期时间)
│
├── SET NX 成功 → 执行业务逻辑
│ │
│ ├── 成功 → 删除锁 key → 返回
│ └── 失败 → 记录异常 → 锁到期释放
│
└── SET NX 失败 → key 已存在(重复请求)→ 直接返回"处理中"
3.4 代码示例
@Component
@Slf4j
public class IdempotentService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String IDEMPOTENT_PREFIX = "idempotent:";
/**
* 执行幂等方法
* @param idempotentKey 幂等 key(如 orderId, paymentId)
* @param expireSec 锁过期时间(秒)
* @param task 要执行的业务逻辑
*/
public <T> IdempotentResult<T> execute(String idempotentKey, long expireSec,
Supplier<T> task) {
String lockKey = IDEMPOTENT_PREFIX + idempotentKey;
String requestId = UUID.randomUUID().toString();
try {
// 尝试获取锁(SET NX + 过期时间)
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, expireSec, TimeUnit.SECONDS);
if (!Boolean.TRUE.equals(success)) {
// 获取锁失败 → 重复请求
log.warn("检测到重复请求: {}", idempotentKey);
return IdempotentResult.repeat("请求正在处理中,请勿重复提交");
}
// 获取锁成功 → 执行业务
T result = task.get();
return IdempotentResult.success(result);
} finally {
// 使用 Lua 脚本释放锁(只释放自己加的锁)
releaseLock(lockKey, requestId);
}
}
private void releaseLock(String key, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key), requestId
);
}
}
@Data
@AllArgsConstructor(staticName = "of")
public class IdempotentResult<T> {
private boolean success;
private boolean repeat;
private T data;
private String message;
public static <T> IdempotentResult<T> success(T data) {
return new IdempotentResult<>(true, false, data, null);
}
public static <T> IdempotentResult<T> repeat(String message) {
return new IdempotentResult<>(false, true, null, message);
}
}
在 Controller 中使用:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private IdempotentService idempotentService;
@Autowired
private OrderService orderService;
@PostMapping("/create")
public Result<?> createOrder(@RequestBody OrderCreateReq req) {
// 用业务唯一标识作为幂等 key(如订单号、token)
String idempotentKey = req.getOrderId();
IdempotentResult<OrderVO> result = idempotentService.execute(
idempotentKey, 30, () -> orderService.createOrder(req)
);
if (result.isRepeat()) {
return Result.warn(result.getMessage());
}
return Result.success(result.getData());
}
}
MQ 消费方使用:
@Component
@Slf4j
public class PaymentCallbackConsumer {
@Autowired
private IdempotentService idempotentService;
@RabbitListener(queues = "payment.callback")
public void onPaymentCallback(PaymentMsg msg) {
// MQ 可能重复消费,用 paymentId 做幂等
idempotentService.execute(
"payment:" + msg.getPaymentId(),
60,
() -> {
paymentService.processCallback(msg);
return true;
}
);
}
}
3.5 优缺点
| 维度 | 说明 |
|---|---|
| ✅ 防止重复提交 | 用户多次点击只处理一次 |
| ✅ 支持分布式 | 多实例部署同样有效 |
| ✅ 通用性强 | 订单、支付、MQ 消费都可复用 |
| ❌ 锁过期风险 | 业务执行超时锁自动释放,后续请求可能进来 |
| ❌ 增加 Redis 依赖 | Redis 不可用时幂等失效 |
| ❌ 需要正确传递幂等 key | 前端需生成唯一 token |
场景四:分布式锁 + 库存扣减(秒杀)
4.1 业务背景
秒杀/抢购场景中,库存扣减是高并发下的典型竞态问题:
夸张的例子:
商品库存剩 1 件
1000 个用户同时抢购
如果不加锁:
线程 A 查库存 = 1 → 扣减 → 库存变 0
线程 B 查库存 = 1(还没被 A 更新)→ 扣减 → 库存变 -1 ❌ 超卖!
4.2 技术背景
库存扣减的三种方案:
| 方案 | 原理 | 性能 | 可靠性 |
|---|---|---|---|
| 数据库行锁 | UPDATE SET stock=stock-1 WHERE stock>0 |
低 | 最高 |
| 分布式锁 | Redis 锁保护扣减过程 | 中 | 高 |
| Lua 脚本 | Redis 原子操作扣减 | 高 | 高 |
推荐方案:Lua 脚本(Redis 原子操作 + 高性能),配合分布式锁做兜底。
4.3 实现思路
用户请求抢购
│
▼
Redis Lua 脚本原子扣减库存
│
├── 扣减成功(库存 > 0)→ 创建订单(DB 写入)
│
└── 扣减失败(库存 = 0)→ 返回已售罄
4.4 代码示例
Lua 脚本库存扣减:
-- stock_decrement.lua
-- KEYS[1]: 库存 key
-- KEYS[2]: 已购买用户 set key
-- ARGV[1]: 用户 ID
-- ARGV[2]: 扣减数量
-- 1. 检查用户是否已购买(防止重复抢购)
local bought = redis.call('SISMEMBER', KEYS[2], ARGV[1])
if bought == 1 then
return -2 -- 已购买过
end
-- 2. 查库存
local stock = redis.call('GET', KEYS[1])
if not stock or tonumber(stock) <= 0 then
return -1 -- 库存不足
end
-- 3. 扣减库存
local remain = redis.call('DECRBY', KEYS[1], ARGV[2])
-- 4. 记录已购买用户
redis.call('SADD', KEYS[2], ARGV[1])
return remain -- 返回剩余库存
Spring 中调用:
@Service
@Slf4j
public class SecKillService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private OrderService orderService;
// 加载 Lua 脚本
private final DefaultRedisScript<Long> stockScript;
public SecKillService() {
stockScript = new DefaultRedisScript<>();
stockScript.setScriptSource(
new ResourceScriptSource(new ClassPathResource("lua/stock_decrement.lua")));
stockScript.setResultType(Long.class);
}
/**
* 秒杀抢购
*/
public Result<String> seckill(Long productId, Long userId) {
String stockKey = "stock:" + productId;
String boughtKey = "bought:" + productId;
// 执行 Lua 脚本(原子操作,无需外部锁)
Long remain = redisTemplate.execute(stockScript,
Arrays.asList(stockKey, boughtKey),
String.valueOf(userId), "1");
if (remain == null) {
return Result.error("系统繁忙");
}
if (remain == -1) {
return Result.error("已售罄");
}
if (remain == -2) {
return Result.warn("您已购买过该商品");
}
// 脚本内扣减成功,创建订单
try {
Order order = orderService.createOrder(productId, userId);
return Result.success("抢购成功", order.getOrderId());
} catch (Exception e) {
// 订单创建失败 → 回滚库存、移除已购买记录
rollback(productId, userId);
log.error("订单创建失败,已回滚库存", e);
return Result.error("系统异常,请重新抢购");
}
}
private void rollback(Long productId, Long userId) {
redisTemplate.opsForValue().increment("stock:" + productId);
redisTemplate.opsForSet().remove("bought:" + productId, String.valueOf(userId));
}
}
使用 Redisson 分布式锁作为兜底(防止 Lua 脚本部署问题):
@Service
public class SecKillWithLockService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private ProductService productService;
public boolean deductStock(Long productId, int quantity) {
RLock lock = redissonClient.getLock("stock:" + productId);
try {
// 尝试获取锁,最多等 1 秒
if (!lock.tryLock(1, 10, TimeUnit.SECONDS)) {
return false;
}
try {
// 查库存 → 扣减(这个过程被锁保护)
int stock = productService.getStock(productId);
if (stock < quantity) {
return false;
}
productService.deductStock(productId, quantity);
return true;
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
4.5 优缺点
| 维度 | 说明 |
|---|---|
| ✅ 防止超卖 | Lua 脚本原子操作,库存扣减精确 |
| ✅ 高性能 | Redis 内存操作 + Lua 脚本一次网络往返 |
| ✅ 防重复抢购 | SISMEMBER 判断是否已购买 |
| ❌ Redis 主从切换丢数据 | 主从异步复制,切换时可能丢失库存数据 |
| ❌ 订单创建失败需回滚 | 分布式事务复杂,需手动补偿 |
| ❌ 恶意用户绕开前端 | 需要额外风控(限流、黑名单) |
场景五:StampedLock + 配置热加载
5.1 业务背景
微服务中配置中心(Nacos / Apollo)的配置变更需要实时生效:
配置中心推送新配置
│
▼
应用接收配置变更
│
├── 写入本地配置对象
└── 后续请求读取最新配置
核心问题:配置写入时,读取线程可能读到不完整的数据。
5.2 技术背景
配置更新场景具有读极多、写极少的特点:
- 读:每次请求都在读取配置(几千次/秒)
- 写:配置变更很少(几天甚至几周一次)
ReentrantReadWriteLock 可以解决,但它存在写饥饿问题——大量读线程持续持有读锁时,写线程可能长时间无法获取写锁。
StampedLock 的乐观读模式在配置场景中更适合:读取配置时不加锁,只在检测到配置变更时退化为悲观读。
5.3 实现思路
读线程:
① tryOptimisticRead() 获取 stamp
② 读取配置数据
③ validate(stamp) 验证
├── 有效 → 直接返回(无锁,性能最高)
└── 无效 → readLock() 悲观读 → 读取 → 释放
写线程(配置变更):
writeLock() → 修改配置 → unlockWrite()
5.4 代码示例
@Component
public class DynamicConfigService<T> {
private volatile T config;
private final StampedLock stampedLock = new StampedLock();
/**
* 乐观读取配置(高并发场景下性能最优)
*/
public T getConfig() {
// ① 乐观读(不加锁)
long stamp = stampedLock.tryOptimisticRead();
T currentConfig = this.config;
// ② 验证:是否有写线程在此期间修改了配置
if (!stampedLock.validate(stamp)) {
// ③ 被修改了,退化为悲观读
stamp = stampedLock.readLock();
try {
currentConfig = this.config;
} finally {
stampedLock.unlockRead(stamp);
}
}
return currentConfig;
}
/**
* 更新配置(由配置中心 Nacos/Apollo 监听器调用)
*/
public void updateConfig(T newConfig) {
long stamp = stampedLock.writeLock();
try {
this.config = newConfig;
log.info("配置已更新: {}", newConfig);
} finally {
stampedLock.unlockWrite(stamp);
}
}
// 获取当前配置(直接 volatile 读,用于不要求一致性的场景)
public T getConfigDirect() {
return config;
}
}
在配置中心 Nacos 中使用:
@Component
public class NacosConfigWatcher {
private final DynamicConfigService<OrderConfig> orderConfigService;
public NacosConfigWatcher(DynamicConfigService<OrderConfig> orderConfigService) {
this.orderConfigService = orderConfigService;
}
// 监听 Nacos 配置变更
@NacosConfigListener(dataId = "order-config.yaml", groupId = "DEFAULT_GROUP")
public void onConfigChange(String newConfig) {
OrderConfig config = YamlUtil.parse(newConfig, OrderConfig.class);
orderConfigService.updateConfig(config);
}
}
业务中使用:
@Service
public class OrderService {
@Autowired
private DynamicConfigService<OrderConfig> configService;
public void processOrder(Order order) {
// 每次请求读取配置(乐观读,几乎无开销)
OrderConfig config = configService.getConfig();
if (order.getAmount().compareTo(config.getMaxAmount()) > 0) {
// 触发风控审核
riskControlService.audit(order);
}
// 处理订单...
}
}
使用 ReadWriteLock 的替代方案(适用于一致性要求更高的场景):
@Component
public class ConfigWithReadWriteLock<T> {
private volatile T config;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock r = rwLock.readLock();
private final Lock w = rwLock.writeLock();
public T getConfig() {
r.lock();
try {
return config;
} finally {
r.unlock();
}
}
public void updateConfig(T newConfig) {
w.lock();
try {
this.config = newConfig;
} finally {
w.unlock();
}
}
}
5.5 优缺点
| 维度 | 说明 |
|---|---|
| ✅ 读性能极高 | 乐观读不加锁,读操作几乎零开销 |
| ✅ 解决写饥饿 | 乐观读不阻塞写线程 |
| ✅ 配置实时生效 | 无需重启应用 |
| ❌ 不可重入 | StampedLock 不支持重入,嵌套调用需注意 |
| ❌ 不支持 Condition | 不能用于等待/通知场景 |
| ❌ 仅限单机 | 只对当前实例有效,分布式配置需配合配置中心 |
总结:五个场景选型对照
| 场景 | 核心问题 | 解决方案 | 使用的锁 | 是否分布式 |
|---|---|---|---|---|
| 定时任务防重复 | 多实例重复执行 | 分布式锁 + @SchedulerLock | Redisson RLock | ✅ 是 |
| 本地缓存刷新 | 缓存击穿、并发加载 | DCL + ReentrantLock | ReentrantLock | ❌ 否 |
| 幂等性控制 | 重复提交、重复支付 | Redis SET NX 分布式锁 | Redis Lock | ✅ 是 |
| 秒杀库存扣减 | 超卖、高并发竞争 | Lua 脚本 + 分布式锁兜底 | Redis Lock / Lua | ✅ 是 |
| 配置热加载 | 读多写极少、写饥饿 | StampedLock 乐观读 | StampedLock | ❌ 否 |