一、Lock4j 简介

Lock4j 是阿里巴巴开源的分布式锁组件,支持 Redis、Zookeeper 等多种实现,与 Spring Boot 无缝集成。

二、快速开始

1. 添加依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

<!-- 如果使用 Redis,需要同时引入 Redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. 配置文件

spring:
  redis:
    host: localhost
    port: 6379

lock4j:
  # 锁的默认过期时间(ms),超时自动释放
  acquire-timeout: 3000
  # 获取锁的超时时间(ms),超时则获取失败
  expire: 30000
  # 主线程等待锁的时间,超时则抛出异常
  primary-timeout: 3000

3. 基础使用

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    // 基本用法:锁的key为 "user:lock:" + id
    @Lock4j(keys = {"#id"}, expire = 10000, acquireTimeout = 3000)
    public void updateUser(Long id, String name) {
        User user = userMapper.selectById(id);
        user.setName(name);
        userMapper.updateById(user);
    }
}

三、核心注解详解

@Lock4j 注解参数

参数 说明 默认值
keys 锁的key,支持SpEL表达式
expire 锁的过期时间(ms) 配置中的值
acquireTimeout 获取锁超时时间(ms) 配置中的值
name 锁的名称前缀

四、实战示例

1. 防重复提交

@RestController
public class OrderController {
    
    @PostMapping("/order/create")
    @Lock4j(keys = {"#userId"}, expire = 5000, acquireTimeout = 1000)
    public Result createOrder(@RequestBody OrderVO orderVO, @RequestParam Long userId) {
        // 业务逻辑
        return Result.success();
    }
}

2. 库存扣减(MyBatis-Plus 操作)

@Service
public class ProductService {
    
    @Autowired
    private ProductMapper productMapper;
    
    @Lock4j(keys = {"#productId"}, expire = 5000, acquireTimeout = 3000)
    @Transactional(rollbackFor = Exception.class)
    public boolean deductStock(Long productId, Integer quantity) {
        // 1. 查询商品
        Product product = productMapper.selectById(productId);
        
        // 2. 检查库存
        if (product.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }
        
        // 3. 扣减库存
        product.setStock(product.getStock() - quantity);
        int result = productMapper.updateById(product);
        
        return result > 0;
    }
}

3. 复杂SpEL表达式

@Service
public class BizService {
    
    // 使用多个参数组成key
    @Lock4j(keys = {"#userId", "#orderId"})
    public void processOrder(Long userId, String orderId) {
        // 业务逻辑
    }
    
    // 使用对象属性
    @Lock4j(keys = {"#user.id", "#user.name"})
    public void updateUser(@Param("user") User user) {
        // 业务逻辑
    }
    
    // 自定义key前缀
    @Lock4j(name = "order:lock", keys = {"#orderId"})
    public void handleOrder(String orderId) {
        // 最终key: order:lock:orderId值
    }
}

4. 手动获取锁

@Service
public class ManualLockService {
    
    @Autowired
    private LockTemplate lockTemplate;
    
    public void doWithLock(String resourceId) {
        // 手动获取锁
        LockInfo lockInfo = lockTemplate.lock(resourceId, 10000L, 3000L);
        
        if (lockInfo == null) {
            throw new RuntimeException("获取锁失败");
        }
        
        try {
            // 业务逻辑
            System.out.println("执行业务逻辑");
        } finally {
            // 释放锁
            lockTemplate.releaseLock(lockInfo);
        }
    }
}

5. 配合 LambdaQueryWrapper

@Service
public class AccountService {
    
    @Autowired
    private AccountMapper accountMapper;
    
    @Lock4j(keys = {"#accountId"}, expire = 10000)
    @Transactional
    public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        // 使用 LambdaQueryWrapper 查询
        LambdaQueryWrapper<Account> fromWrapper = Wrappers.<Account>lambdaQuery()
            .eq(Account::getId, fromAccountId);
        Account fromAccount = accountMapper.selectOne(fromWrapper);
        
        LambdaQueryWrapper<Account> toWrapper = Wrappers.<Account>lambdaQuery()
            .eq(Account::getId, toAccountId);
        Account toAccount = accountMapper.selectOne(toWrapper);
        
        // 扣款
        fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
        accountMapper.updateById(fromAccount);
        
        // 加款
        toAccount.setBalance(toAccount.getBalance().add(amount));
        accountMapper.updateById(toAccount);
    }
}

五、高级配置

1. 自定义Key生成器

@Component
public class CustomKeyGenerator implements LockKeyGenerator {
    
    @Override
    public String generateKey(LockInfo lockInfo, Method method, Object[] args) {
        // 自定义key生成逻辑
        return "custom:" + method.getName() + ":" + Arrays.hashCode(args);
    }
}

// 使用方式
@Lock4j(keys = {"#id"}, keyGenerator = CustomKeyGenerator.class)
public void customKeyMethod(Long id) {
    // ...
}

2. 自定义锁失败处理

@Component
public class CustomLockFailureHandler implements LockFailureHandler {
    
    @Override
    public void onLockFailure(String key, Method method, Object[] args) {
        // 锁获取失败时的处理
        throw new BusinessException("系统繁忙,请稍后重试");
    }
}

3. 执行器配置

@Configuration
public class Lock4jConfig {
    
    @Bean
    public ExecutorService lockExecutor() {
        return Executors.newFixedThreadPool(10);
    }
}

六、注意事项

1. 事务与锁的顺序

// ✅ 正确:锁在外,事务在内
@Lock4j(keys = {"#id"})
@Transactional
public void correctWay(Long id) {
    // 业务逻辑
}

// ❌ 错误:事务在外,锁在内可能导致事务未提交锁已释放
@Transactional
@Lock4j(keys = {"#id"})
public void wrongWay(Long id) {
    // 业务逻辑
}

2. 避免死锁

// 多个锁时注意顺序
@Lock4j(keys = {"#id1"})
public void method1(Long id1, Long id2) {
    // 调用需要锁id2的方法 - 可能导致死锁
    method2(id2);
}

@Lock4j(keys = {"#id2"})
public void method2(Long id2) {
    // ...
}

3. 合理设置超时时间

lock4j:
  # 根据业务执行时间设置,建议比业务执行时间长30%
  expire: 5000  # 业务平均执行3秒,设置5秒
  # 获取锁超时时间不宜过长
  acquire-timeout: 2000

七、常见问题解决

1. 锁未释放问题

// 使用 try-finally 确保释放
LockInfo lockInfo = lockTemplate.lock(key, 10000, 3000);
try {
    // 业务操作
    // 注意:不要在业务代码中提前return
} finally {
    if (lockInfo != null) {
        lockTemplate.releaseLock(lockInfo);
    }
}

2. 重试机制

@Component
public class RetryLockService {
    
    @Autowired
    private LockTemplate lockTemplate;
    
    public void executeWithRetry(String key, Runnable task, int maxRetries) {
        for (int i = 0; i < maxRetries; i++) {
            LockInfo lockInfo = lockTemplate.lock(key, 10000, 3000);
            if (lockInfo != null) {
                try {
                    task.run();
                    return;
                } finally {
                    lockTemplate.releaseLock(lockInfo);
                }
            }
            // 等待后重试
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        }
        throw new RuntimeException("获取锁失败,重试" + maxRetries + "次");
    }
}

八、性能优化建议

  1. 锁粒度:尽量使用细粒度锁,如按订单ID、用户ID

  2. 超时设置:根据业务耗时合理设置,避免过长或过短

  3. 监控告警:添加锁等待时间、失败率的监控

  4. 降级方案:锁服务不可用时,考虑本地锁或限流

九、最佳实践总结

@Service
@Slf4j
public class BestPracticeService {
    
    /**
     * 推荐的最佳实践模板
     */
    @Lock4j(
        keys = {"#bizId"},           // 精确的锁key
        expire = 10000,               // 比业务执行时间长30-50%
        acquireTimeout = 2000         // 合理等待时间
    )
    @Transactional(rollbackFor = Exception.class)
    public Result doBusiness(String bizId, BusinessData data) {
        // 1. 参数校验
        if (StringUtils.isEmpty(bizId)) {
            throw new IllegalArgumentException("业务ID不能为空");
        }
        
        // 2. 业务处理
        try {
            // MyBatis-Plus 数据库操作
            return Result.success();
        } catch (Exception e) {
            log.error("业务处理失败, bizId: {}", bizId, e);
            throw new BusinessException("处理失败");
        }
    }
}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐