← 返回 JUC 列表

Spring Boot / Spring Cloud 中的 Lock 实战场景

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 技术背景

单机方案:使用 ReentrantLocksynchronized —— 但多实例下无效,因为锁只在一个 JVM 内生效。

分布式锁方案:需要一个所有实例都能访问到的公共存储来协调锁:

数据库(悲观锁 SELECT FOR UPDATE)
Redis(SET NX / RedLock)
Zookeeper(临时有序节点)
Etcd(租约机制)

其中最常用的是 Redis 分布式锁(性能高、实现简单)。

1.3 实现思路

定时任务触发
    │
    ▼
尝试获取分布式锁(Redis SET NX + 过期时间)
    │
    ├── 获取成功 → 执行任务 → 执行完毕 → 释放锁
    │
    └── 获取失败 → 跳过(其他实例已执行)

1.4 代码示例

方案 A:手动封装 Redis 分布式锁

java
@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(推荐,自带看门狗)

java
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}
java
@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);
        }
    }
}

定时任务中使用:

java
@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 框架(开箱即用)

java
// 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)减少对下游服务或数据库的调用:

java
// 本地缓存常见场景:字典数据、配置项、白名单、路由表
// 这些数据特点:读极多、写很少、允许短时间不一致

核心问题:缓存刷新时的并发控制

线程 A 读缓存 → 缓存过期 → 去数据库查询 → 回填缓存
线程 B 读缓存 → 缓存过期 → 去数据库查询 → 回填缓存
线程 C 读缓存 → 缓存过期 → 去数据库查询 → 回填缓存

问题:缓存过期瞬间,N 个请求同时穿透到数据库!
这叫「缓存雪崩」或「缓存击穿」

2.2 技术背景

需要保证:缓存刷新时,只有一个线程去加载数据,其他线程等待这个线程的结果——这就是 ReentrantLock 的典型场景。

Caffeine 内置了此机制(expireAfterWrite + 同步加载),但理解其背后的 Lock 原理对排查复杂问题很重要。

2.3 实现思路

读请求
    │
    ▼
缓存命中?───是──→ 直接返回
    │
    否
    │
    ▼
尝试获取锁(tryLock)
    │
    ├── 成功 → 再次检查缓存(double check)
    │            ├── 有其他线程已回填 → 返回
    │            └── 还是没有 → 查 DB → 回填缓存 → 释放锁
    │
    └── 失败 → 等待(等待获取锁的线程完成)
                 → 直接读缓存返回

关键点:双重检查锁(DCL)模式,和单例模式的 DCL 一个原理。

2.4 代码示例

java
@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 属性(更简单的方案):

java
@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 代码示例

java
@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 中使用:

java
@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 消费方使用:

java
@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 脚本库存扣减:

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 中调用:

java
@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 脚本部署问题):

java
@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 代码示例

java
@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 中使用:

java
@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);
    }
}

业务中使用:

java
@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 的替代方案(适用于一致性要求更高的场景):

java
@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 ❌ 否