从分布式锁到Java锁机制

张开发
2026/4/19 12:49:25 15 分钟阅读

分享文章

从分布式锁到Java锁机制
从分布式锁到 Java 锁机制全方位解析并发控制从单机到分布式从 JVM 内置锁到 Redis 分布式锁本文系统梳理 Java 开发中的各种锁机制帮助你建立完整的并发控制知识体系。一、为什么需要锁并发问题的根源在多线程或多节点环境下多个执行单元同时访问共享资源时会出现三大问题问题说明例子可见性一个线程修改了变量其他线程看不到线程 A 改了 flagtrue线程 B 还在用 flagfalse原子性多个操作被中途打断i 不是原子操作多线程并发会丢失更新有序性指令重排序导致逻辑错误new 对象时先分配内存再初始化重排序后可能返回未初始化的对象锁的本质通过互斥机制保证同一时间只有一个执行单元能访问共享资源。二、Java 内置锁机制1. synchronized 关键字基本用法// 修饰实例方法锁当前对象 thispublicsynchronizedvoidmethod(){// 临界区}// 修饰静态方法锁 Class 对象publicstaticsynchronizedvoidstaticMethod(){// 临界区}// 修饰代码块锁指定对象publicvoidmethod(){synchronized(this){// 临界区}}锁升级过程Java 6 优化无锁 → 偏向锁 → 轻量级锁 → 重量级锁锁状态实现方式适用场景偏向锁记录线程 ID无需同步单线程反复访问轻量级锁CAS 自旋竞争不激烈执行时间短重量级锁操作系统 Mutex阻塞线程竞争激烈为什么要锁升级根据竞争程度动态调整锁粒度以最小代价获得最佳性能。底层原理基于Monitor对象实现每个对象都有一个 Monitor线程竞争 Monitor 的所有权2. ReentrantLock可重入锁基本用法ReentrantLocklocknewReentrantLock();lock.lock();try{// 临界区}finally{lock.unlock();// ⭐ 必须在 finally 中释放}高级特性特性说明示例可中断等待锁时可被中断lock.lockInterruptibly()尝试获取尝试获取锁失败不阻塞lock.tryLock()超时获取等待指定时间后放弃lock.tryLock(5, TimeUnit.SECONDS)公平锁按请求顺序获取锁new ReentrantLock(true)条件变量多个等待队列lock.newCondition()与 synchronized 对比特性synchronizedReentrantLock实现层面JVM 内置JDK API锁释放自动释放手动 unlock()公平性非公平锁可选择公平/非公平可中断❌✅尝试获取❌✅条件变量单一 wait/notify多个 Condition使用建议大多数场景用synchronized代码简洁JVM 优化好需要高级功能用ReentrantLock3. volatile 关键字三大特性特性说明是否保证可见性修改后立即刷新到主内存✅有序性禁止指令重排序✅原子性复合操作不被打断❌经典应用DCL 单例publicclassSingleton{privatestaticvolatileSingletoninstance;// ⭐ 必须加 volatilepublicstaticSingletongetInstance(){if(instancenull){synchronized(Singleton.class){if(instancenull){instancenewSingleton();}}}returninstance;}}为什么必须加 volatilenew Singleton()分三步分配内存初始化对象引用指向内存如果重排序成 1→3→2其他线程可能拿到未初始化的对象。4. CAS 无锁机制核心原理CAS Compare And Swap比较并交换包含三个参数V内存中的实际值A预期的旧值B要更新的新值执行逻辑如果 V A则将 V 更新为 B否则什么都不做。Java 中的实现AtomicIntegercountnewAtomicInteger(0);count.incrementAndGet();// 原子自增count.compareAndSet(5,10);// CAS 操作CAS 的问题问题说明解决方案ABA 问题值从 A→B→ACAS 认为没变过AtomicStampedReference带版本号自旋开销竞争激烈时消耗 CPU限制自旋次数单变量限制只能保证一个变量的原子性封装成对象或用锁三、Java 并发工具类1. 线程安全集合集合特点适用场景ConcurrentHashMapCAS synchronized桶级锁高并发 MapCopyOnWriteArrayList写时复制读不加锁读多写少ConcurrentLinkedQueue无界非阻塞队列高并发队列2. 阻塞队列// 有界阻塞队列BlockingQueueStringqueuenewArrayBlockingQueue(100);// 生产者queue.put(data);// 队列满时阻塞// 消费者Stringdataqueue.take();// 队列空时阻塞常见实现ArrayBlockingQueue数组实现有界LinkedBlockingQueue链表实现可选有界/无界SynchronousQueue不存储元素直接移交3. CountDownLatch CyclicBarrier// CountDownLatch倒计时门闩CountDownLatchlatchnewCountDownLatch(3);latch.countDown();// 减 1latch.await();// 等待计数为 0// CyclicBarrier循环屏障CyclicBarrierbarriernewCyclicBarrier(3,()-{System.out.println(所有线程到达屏障);});barrier.await();// 等待其他线程四、分布式锁为什么需要分布式锁单机锁synchronized、ReentrantLock只能控制同一 JVM 内的线程在分布式环境下服务 A节点 1───┐ ├──→ 数据库/Redis共享资源 服务 A节点 2───┘多个节点同时访问共享资源单机锁失效需要分布式锁。分布式锁的要求要求说明互斥性同一时间只能有一个客户端持有锁防死锁即使客户端崩溃锁也能自动释放高可用锁服务本身必须高可用可重入同一个客户端可以重复获取锁可选公平性按请求顺序获取锁可选实现方案对比方案优点缺点适用场景基于数据库简单易懂性能差单点故障低并发场景基于 Redis性能好实现简单极端情况下可能失效大多数场景基于 ZooKeeper强一致性可靠性高性能稍差依赖 ZK高可靠性要求五、基于 Redis 实现分布式锁1. 基础实现setnxpublicbooleantryLock(Stringkey,Stringvalue,longtimeout){// SET key value NX EX timeoutBooleansuccessredisTemplate.opsForValue().setIfAbsent(key,value,timeout,TimeUnit.SECONDS);returnBoolean.TRUE.equals(success);}publicvoidunlock(Stringkey,Stringvalue){// 只能释放自己的锁if(value.equals(redisTemplate.opsForValue().get(key))){redisTemplate.delete(key);}}问题释放锁时可能误删别人的锁锁过期后另一个线程获取了锁当前线程删除了新锁。2. 改进版Lua 脚本保证原子性publicvoidunlock(Stringkey,Stringvalue){StringluaScriptif redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;redisTemplate.execute(newDefaultRedisScript(luaScript,Long.class),Collections.singletonList(key),value);}核心思想判断锁的值是否是自己设置的是才删除用 Lua 脚本保证原子性。3. 完善版Redisson 框架Redisson 是 Redis 官方推荐的 Java 客户端内置分布式锁实现。基本使用// 获取锁对象RLocklockredisson.getLock(myLock);try{// 尝试加锁lock.lock();// 或lock.lock(10, TimeUnit.SECONDS); // 设置过期时间// 临界区// 业务逻辑}finally{// 释放锁lock.unlock();}Redisson 的优势特性说明可重入支持同一个线程多次加锁自动续期看门狗机制防止业务执行时间长导致锁过期公平锁支持公平锁模式Redlock支持 Redlock 算法多节点异步支持提供异步 API看门狗机制// 默认锁过期时间 30 秒// 如果业务执行超过 30 秒看门狗会自动续期// 每次续期到 30 秒直到业务完成或主动释放原理加锁成功后启动一个后台定时任务每隔lockWatchdogTimeout / 3默认 10 秒检查一次如果锁还持有就续期到 30 秒业务完成后停止续期4. Redis 分布式锁的坑问题 1锁过期但业务未执行完现象业务执行时间长于锁过期时间导致锁自动释放其他线程进入临界区。解决方案Redisson 的看门狗机制推荐合理设置过期时间拆分大任务问题 2Redis 主从切换导致锁丢失现象客户端 A 在主节点获取锁主节点将锁同步到从节点前宕机从节点升级为主节点锁丢失客户端 B 也获取到了锁 →互斥性失效解决方案Redlock 算法在多个 Redis 节点上加锁只有超过半数成功才算加锁成功ZooKeeper强一致性不会出现此问题但性能稍差问题 3时钟跳跃现象服务器时间发生跳跃导致锁提前过期。解决方案使用 NTP 同步时间避免在关键业务中依赖绝对时间六、锁机制选型指南单机场景需求推荐方案简单同步synchronized可中断、公平性ReentrantLock状态标志volatile原子操作AtomicInteger等线程隔离ThreadLocal分布式场景需求推荐方案一般并发控制Redis Redisson高可靠性要求ZooKeeper简单场景数据库唯一索引七、实战案例订单防重复提交场景描述用户快速点击提交按钮可能导致重复创建订单。解决方案ServicepublicclassOrderService{AutowiredprivateRedissonClientredissonClient;publicOrderResultcreateOrder(OrderDTOorderDTO){StringuserIdorderDTO.getUserId();StringlockKeyorder:lock:userId;RLocklockredissonClient.getLock(lockKey);try{// 尝试获取锁最多等待 3 秒锁过期时间 10 秒if(lock.tryLock(3,10,TimeUnit.SECONDS)){try{// 1. 检查是否已有进行中的订单OrderexistOrdercheckExistOrder(userId);if(existOrder!null){thrownewBusinessException(订单处理中请勿重复提交);}// 2. 创建订单OrderorderdoCreateOrder(orderDTO);// 3. 返回结果returnnewOrderResult(order);}finally{// 释放锁lock.unlock();}}else{thrownewBusinessException(系统繁忙请稍后重试);}}catch(InterruptedExceptione){Thread.currentThread().interrupt();thrownewBusinessException(系统异常);}}}八、总结锁机制演进路线单机锁 分布式锁 ├─ synchronized ├─ Redis Redisson推荐 ├─ ReentrantLock ├─ ZooKeeper高可靠 ├─ volatile └─ 数据库唯一索引 └─ CAS 原子类核心要点速记知识点核心要点synchronizedJVM 内置自动释放锁升级优化ReentrantLock可中断、可超时、公平锁volatile可见性 有序性不保证原子性CAS无锁原子操作注意 ABA 问题Redis 分布式锁setnx Lua 脚本推荐 Redisson看门狗机制自动续期防止锁过期Redlock多节点加锁解决主从切换问题学习建议先理解单机锁机制synchronized、ReentrantLock掌握并发工具类的使用场景分布式锁优先使用 Redisson了解底层原理结合实际问题选择最合适的方案如果你觉得这篇文章对你有帮助欢迎点赞收藏有疑问欢迎在评论区交流。

更多文章