分布式锁与线程锁的理解和使用一、线程锁本地锁JVM级别理解线程锁用于同一进程内多线程对共享资源的互斥访问保证线程安全。常见的有 synchronized、ReentrantLock、ReadWriteLock 等。例子使用 ReentrantLock 保证库存扣减的线程安全单服务场景。import java.util.concurrent.locks.ReentrantLock; public class InventoryService { private int stock 10; private final ReentrantLock lock new ReentrantLock(); public void decreaseStock(int quantity) { lock.lock(); // 1. 加锁 try { // 2. 操作共享资源 if (stock quantity) { stock - quantity; System.out.println(Thread.currentThread().getName() 扣减成功剩余库存 stock); } else { System.out.println(Thread.currentThread().getName() 库存不足); } } finally { lock.unlock(); // 3. 解锁 } } public static void main(String[] args) { // main方法用于测试 InventoryService service new InventoryService(); // 创建10个线程模拟并发扣减库存 for (int i 0; i 15; i) { new Thread(() - { service.decreaseStock(1); // 循环每个新线程调用 }, 线程- i).start(); } } }测试结果实现了互斥锁因为线程调度是随机的所以资源归属顺序不定二、分布式锁跨服务、跨进程理解在微服务架构中多个服务实例可能同时操作同一共享资源如数据库、Redis、文件存储需要分布式锁来保证互斥。常见实现方式RedisSET NX EX、ZooKeeper、etcd。分布式锁的实现选择本质上是一致性、可用性、性能的权衡。分布式锁通过跨进程协调机制确保同一时间只有一个客户端能访问共享资源常用于分布式事务、幂等控制、并发限流等场景。常见实现方式1. 基于数据库——利用唯一索引或行锁实现互斥唯一索引插入锁记录冲突则获取失败删除记录释放锁。行锁SELECT ... FOR UPDATE在事务中锁定记录。优点实现简单依赖现有数据库。 缺点性能瓶颈明显存在单点风险。2. 基于 Redis——利用SETNX过期时间实现高性能分布式锁加锁SET key value NX PX expireTime 保证原子性。解锁Lua 脚本校验 value客户端ID后删除防止误删。高可用方案RedLock算法在多个 Redis 节点上加锁需多数节点成功。优点高性能部署简单。 缺点弱一致性需处理时钟漂移与主从切换锁丢失问题。3. 基于 ZooKeeper——利用临时顺序节点和事件监听实现强一致性锁客户端创建临时顺序节点判断是否为最小节点是则获取锁否则监听前一节点删除事件。节点断开连接自动删除避免死锁。4. 基于分布式一致性算法Raft/Paxos如etcd、Consul通过日志复制和多数派确认实现强一致性锁适用于金融交易等高一致性场景。 缺点是实现复杂度高性能低于 Redis。例子1使用 Redis 实现分布式锁防止重复下单。import redis.clients.jedis.Jedis; public class RedisDistributedLock { private Jedis jedis new Jedis(localhost, 6379); private final String lockKey order_lock:12345; private final String requestId UUID.randomUUID().toString(); // 加锁超时自动释放避免死锁 public boolean tryLock(long expireMs) { String result jedis.set(lockKey, requestId, NX, PX, expireMs); return OK.equals(result); } // 释放锁使用Lua脚本保证原子性只有持锁者才能释放 public void unlock() { String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; jedis.eval(script, 1, lockKey, requestId); } }使用RedisDistributedLock lock new RedisDistributedLock(); if (lock.tryLock(3000)) { try { // 执行业务创建订单、扣减库存等 } finally { lock.unlock(); } }例子2基于 ZooKeeper 的分布式锁实现原理说明利用 ZooKeeper 的临时顺序节点特性多个客户端在同一个锁节点下创建临时顺序子节点节点序号最小的客户端获得锁其他客户端监听前一个节点的删除事件实现公平的分布式锁。1. 获取锁的核心逻辑// 创建临时顺序节点 String currentPath zk.create(LOCK_ROOT /lock_, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // 获取所有子节点并排序 ListString children zk.getChildren(LOCK_ROOT, false); Collections.sort(children); // 判断是否为最小节点 String currentNode currentPath.substring(currentPath.lastIndexOf(/) 1); int index children.indexOf(currentNode); if (index 0) { // 是最小节点 → 获得锁 return; } else { // 不是最小节点 → 监听前一个节点 String waitPath LOCK_ROOT / children.get(index - 1); CountDownLatch latch new CountDownLatch(1); zk.exists(waitPath, true); // 注册监听 latch.await(); // 阻塞等待 lock(); // 唤醒后重新尝试 }2. 释放锁的核心逻辑// 删除当前节点即释放锁 zk.delete(currentPath, -1);3. 监听回调唤醒等待线程Override public void process(WatchedEvent event) { if (event.getType() Event.EventType.NodeDeleted) { latch.countDown(); // 前一个节点被删除唤醒 } }测试结果三、其他常见锁类型锁类型作用简单例子乐观锁基于版本号更新时检查数据是否被修改UPDATE goods SET stockstock-1, versionversion1 WHERE id1 AND versionold_version悲观锁认为冲突必然发生操作前先锁定数据SELECT * FROM goods WHERE id1 FOR UPDATE读写锁读共享、写互斥提高并发读性能ReentrantReadWriteLock多线程可同时读写时互斥自旋锁不释放CPU循环尝试获取锁适合锁持有时间极短AtomicBoolean while(!lock.compareAndSet(false, true)) {}信号量控制同时访问资源的线程数量Semaphore sem new Semaphore(3); 最多3个线程同时执行synchronizedJava内置锁自动加锁解锁保证线程安全public synchronized void method() { // 临界区 }ReentrantLock可重入锁支持公平/非公平、可中断、超时lock.lock(); try { // 临界区 } finally { lock.unlock(); }CountDownLatch等待多个线程完成任务后继续执行latch.await(); 等待计数归零CyclicBarrier等待多个线程都到达屏障点后一起执行barrier.await(); 等待其他线程到达分布式锁Redis跨服务实例互斥基于Redis原子操作SET lock_key uuid NX PX 30000分布式锁ZooKeeper跨服务实例互斥基于临时顺序节点创建临时顺序节点序号最小获得锁四、总结线程锁适合单机多线程场景无法解决多服务实例的竞争问题。分布式锁适合微服务/分布式系统但需考虑锁超时、误删、可重入、红锁等问题。锁的选择根据业务场景并发量、是否跨服务、资源类型选择合适的锁机制避免性能下降或死锁。线程锁解决单机多线程竞争分布式锁解决多服务实例竞争乐观锁适合读多写少悲观锁适合写多读少读写锁提升读并发信号量实现限流。锁的本质是串行化临界资源访问需根据场景选择合适粒度。
【微服务学习笔记】分布式锁与线程锁的理解和使用
分布式锁与线程锁的理解和使用一、线程锁本地锁JVM级别理解线程锁用于同一进程内多线程对共享资源的互斥访问保证线程安全。常见的有 synchronized、ReentrantLock、ReadWriteLock 等。例子使用 ReentrantLock 保证库存扣减的线程安全单服务场景。import java.util.concurrent.locks.ReentrantLock; public class InventoryService { private int stock 10; private final ReentrantLock lock new ReentrantLock(); public void decreaseStock(int quantity) { lock.lock(); // 1. 加锁 try { // 2. 操作共享资源 if (stock quantity) { stock - quantity; System.out.println(Thread.currentThread().getName() 扣减成功剩余库存 stock); } else { System.out.println(Thread.currentThread().getName() 库存不足); } } finally { lock.unlock(); // 3. 解锁 } } public static void main(String[] args) { // main方法用于测试 InventoryService service new InventoryService(); // 创建10个线程模拟并发扣减库存 for (int i 0; i 15; i) { new Thread(() - { service.decreaseStock(1); // 循环每个新线程调用 }, 线程- i).start(); } } }测试结果实现了互斥锁因为线程调度是随机的所以资源归属顺序不定二、分布式锁跨服务、跨进程理解在微服务架构中多个服务实例可能同时操作同一共享资源如数据库、Redis、文件存储需要分布式锁来保证互斥。常见实现方式RedisSET NX EX、ZooKeeper、etcd。分布式锁的实现选择本质上是一致性、可用性、性能的权衡。分布式锁通过跨进程协调机制确保同一时间只有一个客户端能访问共享资源常用于分布式事务、幂等控制、并发限流等场景。常见实现方式1. 基于数据库——利用唯一索引或行锁实现互斥唯一索引插入锁记录冲突则获取失败删除记录释放锁。行锁SELECT ... FOR UPDATE在事务中锁定记录。优点实现简单依赖现有数据库。 缺点性能瓶颈明显存在单点风险。2. 基于 Redis——利用SETNX过期时间实现高性能分布式锁加锁SET key value NX PX expireTime 保证原子性。解锁Lua 脚本校验 value客户端ID后删除防止误删。高可用方案RedLock算法在多个 Redis 节点上加锁需多数节点成功。优点高性能部署简单。 缺点弱一致性需处理时钟漂移与主从切换锁丢失问题。3. 基于 ZooKeeper——利用临时顺序节点和事件监听实现强一致性锁客户端创建临时顺序节点判断是否为最小节点是则获取锁否则监听前一节点删除事件。节点断开连接自动删除避免死锁。4. 基于分布式一致性算法Raft/Paxos如etcd、Consul通过日志复制和多数派确认实现强一致性锁适用于金融交易等高一致性场景。 缺点是实现复杂度高性能低于 Redis。例子1使用 Redis 实现分布式锁防止重复下单。import redis.clients.jedis.Jedis; public class RedisDistributedLock { private Jedis jedis new Jedis(localhost, 6379); private final String lockKey order_lock:12345; private final String requestId UUID.randomUUID().toString(); // 加锁超时自动释放避免死锁 public boolean tryLock(long expireMs) { String result jedis.set(lockKey, requestId, NX, PX, expireMs); return OK.equals(result); } // 释放锁使用Lua脚本保证原子性只有持锁者才能释放 public void unlock() { String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; jedis.eval(script, 1, lockKey, requestId); } }使用RedisDistributedLock lock new RedisDistributedLock(); if (lock.tryLock(3000)) { try { // 执行业务创建订单、扣减库存等 } finally { lock.unlock(); } }例子2基于 ZooKeeper 的分布式锁实现原理说明利用 ZooKeeper 的临时顺序节点特性多个客户端在同一个锁节点下创建临时顺序子节点节点序号最小的客户端获得锁其他客户端监听前一个节点的删除事件实现公平的分布式锁。1. 获取锁的核心逻辑// 创建临时顺序节点 String currentPath zk.create(LOCK_ROOT /lock_, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // 获取所有子节点并排序 ListString children zk.getChildren(LOCK_ROOT, false); Collections.sort(children); // 判断是否为最小节点 String currentNode currentPath.substring(currentPath.lastIndexOf(/) 1); int index children.indexOf(currentNode); if (index 0) { // 是最小节点 → 获得锁 return; } else { // 不是最小节点 → 监听前一个节点 String waitPath LOCK_ROOT / children.get(index - 1); CountDownLatch latch new CountDownLatch(1); zk.exists(waitPath, true); // 注册监听 latch.await(); // 阻塞等待 lock(); // 唤醒后重新尝试 }2. 释放锁的核心逻辑// 删除当前节点即释放锁 zk.delete(currentPath, -1);3. 监听回调唤醒等待线程Override public void process(WatchedEvent event) { if (event.getType() Event.EventType.NodeDeleted) { latch.countDown(); // 前一个节点被删除唤醒 } }测试结果三、其他常见锁类型锁类型作用简单例子乐观锁基于版本号更新时检查数据是否被修改UPDATE goods SET stockstock-1, versionversion1 WHERE id1 AND versionold_version悲观锁认为冲突必然发生操作前先锁定数据SELECT * FROM goods WHERE id1 FOR UPDATE读写锁读共享、写互斥提高并发读性能ReentrantReadWriteLock多线程可同时读写时互斥自旋锁不释放CPU循环尝试获取锁适合锁持有时间极短AtomicBoolean while(!lock.compareAndSet(false, true)) {}信号量控制同时访问资源的线程数量Semaphore sem new Semaphore(3); 最多3个线程同时执行synchronizedJava内置锁自动加锁解锁保证线程安全public synchronized void method() { // 临界区 }ReentrantLock可重入锁支持公平/非公平、可中断、超时lock.lock(); try { // 临界区 } finally { lock.unlock(); }CountDownLatch等待多个线程完成任务后继续执行latch.await(); 等待计数归零CyclicBarrier等待多个线程都到达屏障点后一起执行barrier.await(); 等待其他线程到达分布式锁Redis跨服务实例互斥基于Redis原子操作SET lock_key uuid NX PX 30000分布式锁ZooKeeper跨服务实例互斥基于临时顺序节点创建临时顺序节点序号最小获得锁四、总结线程锁适合单机多线程场景无法解决多服务实例的竞争问题。分布式锁适合微服务/分布式系统但需考虑锁超时、误删、可重入、红锁等问题。锁的选择根据业务场景并发量、是否跨服务、资源类型选择合适的锁机制避免性能下降或死锁。线程锁解决单机多线程竞争分布式锁解决多服务实例竞争乐观锁适合读多写少悲观锁适合写多读少读写锁提升读并发信号量实现限流。锁的本质是串行化临界资源访问需根据场景选择合适粒度。