找回密码
 立即注册
首页 业界区 业界 Java锁这样用,从单机到分布式一步到位

Java锁这样用,从单机到分布式一步到位

欧阳梓蓓 2025-9-26 10:48:12
Java锁这样用,从单机到分布式一步到位

单机锁已经不够用了?分布式系统中如何保证数据安全?今天我们来聊聊从单机锁到分布式锁的完整解决方案,最后用一个注解就能搞定所有锁的问题!
为什么需要锁?

在多线程或多进程环境中,多个操作同时访问同一资源时可能出现数据不一致的问题。锁就是用来保证同一时间只有一个操作能访问共享资源。
锁的作用:

  • 保证数据一致性
  • 防止并发冲突
  • 确保操作的原子性
简单理解: 就像厕所门上的锁,同一时间只能有一个人使用,其他人必须等待。
单机锁的局限性

synchronized关键字

Java最简单的锁机制。
  1. public class CounterService {
  2.     private int count = 0;
  3.    
  4.     public synchronized void increment() {
  5.         count++;
  6.     }
  7.    
  8.     public synchronized int getCount() {
  9.         return count;
  10.     }
  11. }
复制代码
ReentrantLock可重入锁

更灵活的锁机制。
  1. public class CounterService {
  2.     private int count = 0;
  3.     private final ReentrantLock lock = new ReentrantLock();
  4.    
  5.     public void increment() {
  6.         lock.lock();
  7.         try {
  8.             count++;
  9.         } finally {
  10.             lock.unlock();
  11.         }
  12.     }
  13. }
复制代码
单机锁的问题:

  • 只能在单个JVM内生效
  • 多个服务实例之间无法互斥
  • 分布式环境下失效
分布式环境的挑战

当应用部署在多台服务器上时,单机锁就不够用了。
1.png

分布式环境下的问题:

  • 多个服务实例可能同时执行相同操作
  • 库存扣减、订单生成等场景容易出现数据不一致
  • 需要跨JVM的锁机制
基于Redis的分布式锁

简单的Redis分布式锁

使用Redis的SET命令实现。
  1. @Component
  2. public class SimpleRedisLock {
  3.    
  4.     @Autowired
  5.     private StringRedisTemplate redisTemplate;
  6.    
  7.     public boolean tryLock(String key, String value, long expireTime) {
  8.         Boolean result = redisTemplate.opsForValue()
  9.             .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
  10.         return Boolean.TRUE.equals(result);
  11.     }
  12.    
  13.     public void releaseLock(String key, String value) {
  14.         String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
  15.                        "return redis.call('del', KEYS[1]) else return 0 end";
  16.         redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
  17.                             Arrays.asList(key), value);
  18.     }
  19. }
复制代码
使用示例
  1. @Service
  2. public class OrderService {
  3.    
  4.     @Autowired
  5.     private SimpleRedisLock redisLock;
  6.    
  7.     public void createOrder(Long userId) {
  8.         String lockKey = "order:user:" + userId;
  9.         String lockValue = UUID.randomUUID().toString();
  10.         
  11.         if (redisLock.tryLock(lockKey, lockValue, 30)) {
  12.             try {
  13.                 // 执行订单创建逻辑
  14.                 doCreateOrder(userId);
  15.             } finally {
  16.                 redisLock.releaseLock(lockKey, lockValue);
  17.             }
  18.         } else {
  19.             throw new RuntimeException("获取锁失败,请稍后重试");
  20.         }
  21.     }
  22.    
  23.     private void doCreateOrder(Long userId) {
  24.         // 具体的订单创建逻辑
  25.     }
  26. }
复制代码
基于Redisson的分布式锁

Redisson提供了更完善的分布式锁实现。
引入依赖
  1. <dependency>
  2.     <groupId>org.redisson</groupId>
  3.     redisson-spring-boot-starter</artifactId>
  4.     <version>3.20.1</version>
  5. </dependency>
复制代码
配置Redisson
  1. @Configuration
  2. public class RedissonConfig {
  3.    
  4.     @Bean
  5.     public RedissonClient redissonClient() {
  6.         Config config = new Config();
  7.         config.useSingleServer()
  8.               .setAddress("redis://localhost:6379")
  9.               .setDatabase(0);
  10.         return Redisson.create(config);
  11.     }
  12. }
复制代码
使用Redisson锁
  1. @Service
  2. public class OrderService {
  3.    
  4.     @Autowired
  5.     private RedissonClient redissonClient;
  6.    
  7.     public void createOrder(Long userId) {
  8.         String lockKey = "order:user:" + userId;
  9.         RLock lock = redissonClient.getLock(lockKey);
  10.         
  11.         try {
  12.             if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
  13.                 // 执行订单创建逻辑
  14.                 doCreateOrder(userId);
  15.             } else {
  16.                 throw new RuntimeException("获取锁失败,请稍后重试");
  17.             }
  18.         } catch (InterruptedException e) {
  19.             Thread.currentThread().interrupt();
  20.             throw new RuntimeException("获取锁被中断");
  21.         } finally {
  22.             if (lock.isHeldByCurrentThread()) {
  23.                 lock.unlock();
  24.             }
  25.         }
  26.     }
  27. }
复制代码
Redisson的优势:

  • 自动续期机制
  • 可重入锁支持
  • 公平锁、读写锁等多种锁类型
  • 异常处理更完善
注解式分布式锁工具

手动加锁解锁容易出错,我们可以通过注解来简化使用。
自定义锁注解
  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface DistributedLock {
  4.    
  5.     String key() default "";
  6.    
  7.     long waitTime() default 10;
  8.    
  9.     long leaseTime() default 30;
  10.    
  11.     TimeUnit timeUnit() default TimeUnit.SECONDS;
  12.    
  13.     String errorMessage() default "获取锁失败,请稍后重试";
  14. }
复制代码
AOP切面实现
  1. @Aspect
  2. @Component
  3. public class DistributedLockAspect {
  4.    
  5.     @Autowired
  6.     private RedissonClient redissonClient;
  7.    
  8.     @Around("@annotation(distributedLock)")
  9.     public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
  10.         String lockKey = generateLockKey(joinPoint, distributedLock.key());
  11.         RLock lock = redissonClient.getLock(lockKey);
  12.         
  13.         try {
  14.             boolean acquired = lock.tryLock(
  15.                 distributedLock.waitTime(),
  16.                 distributedLock.leaseTime(),
  17.                 distributedLock.timeUnit()
  18.             );
  19.             
  20.             if (!acquired) {
  21.                 throw new RuntimeException(distributedLock.errorMessage());
  22.             }
  23.             
  24.             return joinPoint.proceed();
  25.             
  26.         } catch (InterruptedException e) {
  27.             Thread.currentThread().interrupt();
  28.             throw new RuntimeException("获取锁被中断");
  29.         } finally {
  30.             if (lock.isHeldByCurrentThread()) {
  31.                 lock.unlock();
  32.             }
  33.         }
  34.     }
  35.    
  36.     private String generateLockKey(ProceedingJoinPoint joinPoint, String key) {
  37.         if (StringUtils.hasText(key)) {
  38.             return parseKey(key, joinPoint);
  39.         }
  40.         
  41.         String className = joinPoint.getTarget().getClass().getSimpleName();
  42.         String methodName = joinPoint.getSignature().getName();
  43.         return className + ":" + methodName;
  44.     }
  45.    
  46.     private String parseKey(String key, ProceedingJoinPoint joinPoint) {
  47.         if (key.contains("#")) {
  48.             // 支持SpEL表达式解析参数
  49.             return parseSpEL(key, joinPoint);
  50.         }
  51.         return key;
  52.     }
  53.    
  54.     private String parseSpEL(String key, ProceedingJoinPoint joinPoint) {
  55.         // SpEL表达式解析实现
  56.         // 这里简化处理,实际项目中可以使用Spring的SpEL解析器
  57.         return key.replace("#userId", String.valueOf(joinPoint.getArgs()[0]));
  58.     }
  59. }
复制代码
使用注解式分布式锁
  1. @Service
  2. public class OrderService {
  3.    
  4.     @DistributedLock(key = "order:user:#userId", waitTime = 5, leaseTime = 30)
  5.     public void createOrder(Long userId) {
  6.         // 方法执行时自动加锁
  7.         doCreateOrder(userId);
  8.         // 方法执行完成后自动释放锁
  9.     }
  10.    
  11.     @DistributedLock(key = "inventory:product:#productId")
  12.     public void decreaseInventory(Long productId, Integer quantity) {
  13.         // 库存扣减逻辑
  14.         doDecreaseInventory(productId, quantity);
  15.     }
  16.    
  17.     private void doCreateOrder(Long userId) {
  18.         // 具体的订单创建逻辑
  19.     }
  20.    
  21.     private void doDecreaseInventory(Long productId, Integer quantity) {
  22.         // 具体的库存扣减逻辑
  23.     }
  24. }
复制代码
分布式锁的注意事项

1. 锁超时时间设置

锁的超时时间要根据业务执行时间合理设置。
  1. // 根据业务复杂度设置合适的超时时间
  2. @DistributedLock(key = "complex:task:#taskId", leaseTime = 60) // 复杂任务60秒
  3. public void executeComplexTask(String taskId) {
  4.     // 复杂业务逻辑
  5. }
  6. @DistributedLock(key = "simple:task:#taskId", leaseTime = 10) // 简单任务10秒
  7. public void executeSimpleTask(String taskId) {
  8.     // 简单业务逻辑
  9. }
复制代码
2. 锁的粒度控制

锁的粒度要合适,既要保证安全性,又要避免性能问题。
  1. // 细粒度锁 - 针对具体用户
  2. @DistributedLock(key = "user:operation:#userId")
  3. public void userOperation(Long userId) {
  4.     // 只锁定特定用户的操作
  5. }
  6. // 粗粒度锁 - 全局锁(慎用)
  7. @DistributedLock(key = "global:operation")
  8. public void globalOperation() {
  9.     // 全局操作,会影响所有用户
  10. }
复制代码
3. 异常处理

确保在异常情况下锁能正确释放。
  1. @DistributedLock(key = "order:#orderId", errorMessage = "订单正在处理中,请勿重复操作")
  2. public void processOrder(Long orderId) {
  3.     try {
  4.         // 业务逻辑
  5.         doProcessOrder(orderId);
  6.     } catch (Exception e) {
  7.         // 记录日志
  8.         log.error("订单处理失败: {}", orderId, e);
  9.         throw e; // 重新抛出异常,确保事务回滚
  10.     }
  11.     // 锁会在方法结束时自动释放
  12. }
复制代码
性能优化建议

1. 连接池配置
  1. @Configuration
  2. public class RedissonConfig {
  3.    
  4.     @Bean
  5.     public RedissonClient redissonClient() {
  6.         Config config = new Config();
  7.         config.useSingleServer()
  8.               .setAddress("redis://localhost:6379")
  9.               .setConnectionPoolSize(50)    // 连接池大小
  10.               .setConnectionMinimumIdleSize(10); // 最小空闲连接
  11.         return Redisson.create(config);
  12.     }
  13. }
复制代码
2. 锁等待策略
  1. // 快速失败策略
  2. @DistributedLock(key = "quick:#id", waitTime = 0)
  3. public void quickOperation(String id) {
  4.     // 不等待,立即返回
  5. }
  6. // 适度等待策略
  7. @DistributedLock(key = "normal:#id", waitTime = 3)
  8. public void normalOperation(String id) {
  9.     // 等待3秒
  10. }
复制代码
总结

Java锁的演进过程:
单机锁:

  • synchronized、ReentrantLock
  • 只能在单个JVM内使用
分布式锁:

  • 基于Redis实现
  • 支持跨JVM协调
注解式分布式锁:

  • 使用简单,一个注解搞定
  • 减少重复代码,降低出错概率
选择建议:

  • 单机应用:使用synchronized或ReentrantLock
  • 分布式应用:使用Redisson分布式锁
  • 追求简洁:使用注解式分布式锁
掌握这套锁的升级方案,让你的应用在任何环境下都能保证数据安全!
如果这篇文章对你有帮助,请不要忘记:
<ul>
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册