在分布式系统中,多个服务实例可能同时访问共享资源(如数据库、缓存、订单状态等),如何保证操作的原子性和一致性?分布式锁是解决这一问题的核心手段。

本文将带你使用 RedissonLock4j 快速实现一个高效、可靠的分布式锁,并通过实际测试验证其并发控制能力。整个过程简单直观,适合大多数 Java 开发者快速集成。


一、技术选型:为什么选择 Redisson + Lock4j?

  • Redisson:基于 Redis 的 Java 客户端,提供了高性能的分布式锁(支持可重入、公平锁、自动续期等),是分布式锁的工业级实现。
  • Lock4j:一个轻量级的分布式锁框架,支持通过注解方式加锁,极大简化了开发。

二者结合,既能享受注解的便捷性,又能利用 Redisson 的稳定性,是 Spring Boot 项目中实现分布式锁的黄金组合


二、环境准备与依赖引入

pom.xml 中添加以下依赖:

<!-- Lock4j 基于 Redisson 的分布式锁 starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
    <version>2.2.7</version>
</dependency>

<!-- Redisson Spring Boot Starter -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.29.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-data-30</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 兼容 Spring Boot 2.7 的 Spring Data Redis 版本 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-data-27</artifactId>
    <version>3.29.0</version>
</dependency>

提示:如果你使用的是 Spring Boot 3.x,请使用 redisson-spring-data-30


三、配置 Redis 连接

application.yml 中配置 Redis 地址:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    # password: 123456 (如有密码)

Lock4j 和 Redisson 会自动读取配置并初始化,无需额外编码。


四、使用 @Lock4j 注解加锁

只需在需要加锁的方法上添加 @Lock4j 注解即可。

示例代码:

import com.baomidou.lock.annotation.Lock4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {

    @Lock4j(
        keys = {"#orderId"},        // 锁的 key,支持 SpEL 表达式
        expire = 60000,             // 锁过期时间:60秒
        acquireTimeout = 1000       // 获取锁超时时间:1秒
    )
    @PostMapping("/test")
    public R<String> test(@RequestParam String orderId) {
        log.info("【开始】线程 {} 开始处理订单 {}", Thread.currentThread().getName(), orderId);
        try {
            Thread.sleep(500); // 模拟业务耗时
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        log.info("【结束】线程 {} 完成订单 {}", Thread.currentThread().getName(), orderId);
        return R.success("操作成功");
    }
}

参数说明:

参数说明
keys锁的键名,支持 SpEL 表达式,如 #orderId
expire锁自动释放时间(毫秒),防止死锁
acquireTimeout获取锁的最长等待时间,超时则抛出异常

五、keys 属性的常用格式(简单实用版)

keys 决定了锁的作用范围。以下是几种最常用的写法:

1. 基本参数:锁定单个资源

@Lock4j(keys = {"#orderId"})
public void processOrder(String orderId) { ... }
  • 所有传入相同 orderId 的请求互斥执行。

2. 组合参数:锁定多个维度

@Lock4j(keys = {"#userId", "#itemId"})
public void addToCart(Long userId, Long itemId) { ... }
  • 只有当 userId 和 itemId 都相同时才互斥,防止同一用户重复添加同一商品。

3. 访问对象属性

public class OrderRequest {
    private String orderId;
    private Long userId;
    // getter/setter...
}

@Lock4j(keys = {"#request.orderId"})
public void createOrder(OrderRequest request) { ... }
  • 使用对象的某个字段作为锁键。

4. 添加业务前缀(推荐)

@Lock4j(keys = {"'ORDER:' + #orderId"})
public void processOrder(String orderId) { ... }
  • 生成的 key 为 ORDER:1001,便于在 Redis 中识别和管理。

六、全局异常处理:友好提示锁失败

当获取锁超时,会抛出 LockFailureException,我们通过全局异常处理器统一处理。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(LockFailureException.class)
    public R<String> handleLockFailure(LockFailureException e) {
        log.warn("获取锁失败", e);
        return R.error("系统繁忙,请稍后重试");
    }

    @ExceptionHandler(Exception.class)
    public R<String> handleException(Exception e) {
        log.error("系统异常", e);
        return R.error("请求失败,请重试");
    }
}

这样用户不会看到“500错误”,而是收到“系统繁忙”的友好提示。


七、并发测试:验证锁的效果

使用 ApifoxPostman 发起并发请求:

  • 请求地址:POST /order/test?orderId=1001
  • 并发数:4
  • 结果:
    • 3 个请求成功执行(串行处理)
    • 1 个请求返回“系统繁忙,请稍后重试”

结论:分布式锁生效,同一订单不会被并发处理。


八、总结

使用 Redisson + Lock4j 实现分布式锁,只需 3 步

  1. 引入依赖
  2. 配置 Redis
  3. 添加 @Lock4j 注解

它具有以下优势:

  • ✅ 简单:注解驱动,无需手动加锁/释放
  • ✅ 安全:基于 Redisson,支持自动续期,防止死锁
  • ✅ 灵活:通过 keys 精确控制锁的粒度
  • ✅ 友好:异常统一处理,提升用户体验

适用于:订单处理、库存扣减、用户幂等、定时任务防重复等场景。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐