写点什么

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

  • 2025-07-15
    福建
  • 本文字数:4759 字

    阅读完需:约 16 分钟

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


为什么需要锁?


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

锁的作用:

  • 保证数据一致性

  • 防止并发冲突

  • 确保操作的原子性

简单理解: 就像厕所门上的锁,同一时间只能有一个人使用,其他人必须等待。


单机锁的局限性


synchronized 关键字

Java 最简单的锁机制。

public class CounterService {    private int count = 0;        public synchronized void increment() {        count++;    }        public synchronized int getCount() {        return count;    }}
复制代码


ReentrantLock 可重入锁

更灵活的锁机制。

public class CounterService {    private int count = 0;    private final ReentrantLock lock = new ReentrantLock();        public void increment() {        lock.lock();        try {            count++;        } finally {            lock.unlock();        }    }}
复制代码


单机锁的问题:

  • 只能在单个 JVM 内生效

  • 多个服务实例之间无法互斥

  • 分布式环境下失效


分布式环境的挑战


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



分布式环境下的问题:

  • 多个服务实例可能同时执行相同操作

  • 库存扣减、订单生成等场景容易出现数据不一致

  • 需要跨 JVM 的锁机制


基于 Redis 的分布式锁


简单的 Redis 分布式锁

使用 Redis 的 SET 命令实现。

@Componentpublic class SimpleRedisLock {        @Autowired    private StringRedisTemplate redisTemplate;        public boolean tryLock(String key, String value, long expireTime) {        Boolean result = redisTemplate.opsForValue()            .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);        return Boolean.TRUE.equals(result);    }        public void releaseLock(String key, String value) {        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),                             Arrays.asList(key), value);    }}
复制代码


使用示例

@Servicepublic class OrderService {        @Autowired    private SimpleRedisLock redisLock;        public void createOrder(Long userId) {        String lockKey = "order:user:" + userId;        String lockValue = UUID.randomUUID().toString();                if (redisLock.tryLock(lockKey, lockValue, 30)) {            try {                // 执行订单创建逻辑                doCreateOrder(userId);            } finally {                redisLock.releaseLock(lockKey, lockValue);            }        } else {            throw new RuntimeException("获取锁失败,请稍后重试");        }    }        private void doCreateOrder(Long userId) {        // 具体的订单创建逻辑    }}
复制代码


基于 Redisson 的分布式锁


Redisson 提供了更完善的分布式锁实现。

引入依赖

<dependency>    <groupId>org.redisson</groupId>    <artifactId>redisson-spring-boot-starter</artifactId>    <version>3.20.1</version></dependency>
复制代码


配置 Redisson

@Configurationpublic class RedissonConfig {        @Bean    public RedissonClient redissonClient() {        Config config = new Config();        config.useSingleServer()              .setAddress("redis://localhost:6379")              .setDatabase(0);        return Redisson.create(config);    }}
复制代码


使用 Redisson 锁

@Servicepublic class OrderService {        @Autowired    private RedissonClient redissonClient;        public void createOrder(Long userId) {        String lockKey = "order:user:" + userId;        RLock lock = redissonClient.getLock(lockKey);                try {            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {                // 执行订单创建逻辑                doCreateOrder(userId);            } else {                throw new RuntimeException("获取锁失败,请稍后重试");            }        } catch (InterruptedException e) {            Thread.currentThread().interrupt();            throw new RuntimeException("获取锁被中断");        } finally {            if (lock.isHeldByCurrentThread()) {                lock.unlock();            }        }    }}
复制代码


Redisson 的优势:

  • 自动续期机制

  • 可重入锁支持

  • 公平锁、读写锁等多种锁类型

  • 异常处理更完善


注解式分布式锁工具


手动加锁解锁容易出错,我们可以通过注解来简化使用。

自定义锁注解

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface DistributedLock {        String key() default "";        long waitTime() default 10;        long leaseTime() default 30;        TimeUnit timeUnit() default TimeUnit.SECONDS;        String errorMessage() default "获取锁失败,请稍后重试";}
复制代码


AOP 切面实现

@Aspect@Componentpublic class DistributedLockAspect {        @Autowired    private RedissonClient redissonClient;        @Around("@annotation(distributedLock)")    public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {        String lockKey = generateLockKey(joinPoint, distributedLock.key());        RLock lock = redissonClient.getLock(lockKey);                try {            boolean acquired = lock.tryLock(                distributedLock.waitTime(),                 distributedLock.leaseTime(),                 distributedLock.timeUnit()            );                        if (!acquired) {                throw new RuntimeException(distributedLock.errorMessage());            }                        return joinPoint.proceed();                    } catch (InterruptedException e) {            Thread.currentThread().interrupt();            throw new RuntimeException("获取锁被中断");        } finally {            if (lock.isHeldByCurrentThread()) {                lock.unlock();            }        }    }        private String generateLockKey(ProceedingJoinPoint joinPoint, String key) {        if (StringUtils.hasText(key)) {            return parseKey(key, joinPoint);        }                String className = joinPoint.getTarget().getClass().getSimpleName();        String methodName = joinPoint.getSignature().getName();        return className + ":" + methodName;    }        private String parseKey(String key, ProceedingJoinPoint joinPoint) {        if (key.contains("#")) {            // 支持SpEL表达式解析参数            return parseSpEL(key, joinPoint);        }        return key;    }        private String parseSpEL(String key, ProceedingJoinPoint joinPoint) {        // SpEL表达式解析实现        // 这里简化处理,实际项目中可以使用Spring的SpEL解析器        return key.replace("#userId", String.valueOf(joinPoint.getArgs()[0]));    }}
复制代码


使用注解式分布式锁

@Servicepublic class OrderService {        @DistributedLock(key = "order:user:#userId", waitTime = 5, leaseTime = 30)    public void createOrder(Long userId) {        // 方法执行时自动加锁        doCreateOrder(userId);        // 方法执行完成后自动释放锁    }        @DistributedLock(key = "inventory:product:#productId")    public void decreaseInventory(Long productId, Integer quantity) {        // 库存扣减逻辑        doDecreaseInventory(productId, quantity);    }        private void doCreateOrder(Long userId) {        // 具体的订单创建逻辑    }        private void doDecreaseInventory(Long productId, Integer quantity) {        // 具体的库存扣减逻辑    }}
复制代码


分布式锁的注意事项


1. 锁超时时间设置

锁的超时时间要根据业务执行时间合理设置。

// 根据业务复杂度设置合适的超时时间@DistributedLock(key = "complex:task:#taskId", leaseTime = 60) // 复杂任务60秒public void executeComplexTask(String taskId) {    // 复杂业务逻辑}
@DistributedLock(key = "simple:task:#taskId", leaseTime = 10) // 简单任务10秒public void executeSimpleTask(String taskId) { // 简单业务逻辑}
复制代码


2. 锁的粒度控制

锁的粒度要合适,既要保证安全性,又要避免性能问题。

// 细粒度锁 - 针对具体用户@DistributedLock(key = "user:operation:#userId")public void userOperation(Long userId) {    // 只锁定特定用户的操作}
// 粗粒度锁 - 全局锁(慎用)@DistributedLock(key = "global:operation")public void globalOperation() { // 全局操作,会影响所有用户}
复制代码


3. 异常处理

确保在异常情况下锁能正确释放。

@DistributedLock(key = "order:#orderId", errorMessage = "订单正在处理中,请勿重复操作")public void processOrder(Long orderId) {    try {        // 业务逻辑        doProcessOrder(orderId);    } catch (Exception e) {        // 记录日志        log.error("订单处理失败: {}", orderId, e);        throw e; // 重新抛出异常,确保事务回滚    }    // 锁会在方法结束时自动释放}
复制代码


性能优化建议


1. 连接池配置

@Configurationpublic class RedissonConfig {        @Bean    public RedissonClient redissonClient() {        Config config = new Config();        config.useSingleServer()              .setAddress("redis://localhost:6379")              .setConnectionPoolSize(50)    // 连接池大小              .setConnectionMinimumIdleSize(10); // 最小空闲连接        return Redisson.create(config);    }}
复制代码


2. 锁等待策略

// 快速失败策略@DistributedLock(key = "quick:#id", waitTime = 0)public void quickOperation(String id) {    // 不等待,立即返回}
// 适度等待策略@DistributedLock(key = "normal:#id", waitTime = 3)public void normalOperation(String id) { // 等待3秒}
复制代码


总结


Java 锁的演进过程:


单机锁:

  • synchronized、ReentrantLock

  • 只能在单个 JVM 内使用


分布式锁:

  • 基于 Redis 实现

  • 支持跨 JVM 协调


注解式分布式锁:

  • 使用简单,一个注解搞定

  • 减少重复代码,降低出错概率


选择建议:

  • 单机应用:使用 synchronized 或 ReentrantLock

  • 分布式应用:使用 Redisson 分布式锁

  • 追求简洁:使用注解式分布式锁


掌握这套锁的升级方案,让你的应用在任何环境下都能保证数据安全!


文章转载自:大毛啊

原文链接:https://www.cnblogs.com/damaoa/p/18983540

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2025-04-01 加入

还未添加个人简介

评论

发布
暂无评论
Java锁这样用,从单机到分布式一步到位_Java_电子尖叫食人鱼_InfoQ写作社区