news 2026/7/2 4:45:05

【微服务学习笔记】分布式锁与线程锁的理解和使用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【微服务学习笔记】分布式锁与线程锁的理解和使用

分布式锁与线程锁的理解和使用

一、线程锁(本地锁,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、文件存储),需要分布式锁来保证互斥。常见实现方式:Redis(SET 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)etcdConsul,通过日志复制和多数派确认实现强一致性锁,适用于金融交易等高一致性场景。 缺点是实现复杂度高,性能低于 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); // 获取所有子节点并排序 List<String> 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 stock=stock-1, version=version+1 WHERE id=1 AND version=old_version

悲观锁

认为冲突必然发生,操作前先锁定数据

SELECT * FROM goods WHERE id=1 FOR UPDATE

读写锁

读共享、写互斥,提高并发读性能

ReentrantReadWriteLock:多线程可同时读,写时互斥

自旋锁

不释放CPU,循环尝试获取锁(适合锁持有时间极短)

AtomicBoolean + while(!lock.compareAndSet(false, true)) {}

信号量

控制同时访问资源的线程数量

Semaphore sem = new Semaphore(3); 最多3个线程同时执行

synchronized

Java内置锁,自动加锁解锁,保证线程安全

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)

跨服务实例互斥,基于临时顺序节点

创建临时顺序节点,序号最小获得锁

四、总结

  1. 线程锁:适合单机多线程场景,无法解决多服务实例的竞争问题。
  2. 分布式锁:适合微服务/分布式系统,但需考虑锁超时、误删、可重入、红锁等问题。
  3. 锁的选择:根据业务场景(并发量、是否跨服务、资源类型)选择合适的锁机制,避免性能下降或死锁。
  4. 线程锁解决单机多线程竞争,分布式锁解决多服务实例竞争;乐观锁适合读多写少,悲观锁适合写多读少;读写锁提升读并发,信号量实现限流。锁的本质是"串行化临界资源访问",需根据场景选择合适粒度。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 4:44:42

MAC多微信登录

# 1. 安装工具 brew install sunnyyoung/repo/wechattweak-cli# 2. 安装插件&#xff08;需要输入密码&#xff09; sudo wechattweak-cli install# 3. 完全退出微信&#xff0c;重新打开现在你的 Mac 上可以同时运行两个微信了&#xff01;### 当前状态 - **第一个微信**: /Ap…

作者头像 李华
网站建设 2026/7/2 4:41:17

GraphRAG 实战:知识图谱和 RAG 结合起来,把核心能力写进作品集

聊《GraphRAG 实战&#xff1a;知识图谱和 RAG 结合起来&#xff0c;把核心能力写进作品集》之前&#xff0c;先说一句实在的&#xff1a;别急着背概念&#xff0c;先看它在真实项目里到底解决什么问题。摘要这篇面向需要构建企业知识库和复杂问答系统的开发者&#xff0c;但不…

作者头像 李华
网站建设 2026/7/2 4:39:37

制造业品牌策划设计:视维助力工厂从“代工“走向“品牌“

东莞作为"世界工厂"&#xff0c;聚集着数十万家制造型企业。过去几十年&#xff0c;这里的大多数企业靠接单代工、拼产能、压成本活得很扎实&#xff1b;但到了今天&#xff0c;原材料、人力、流量成本全线上涨&#xff0c;海外订单波动&#xff0c;单纯"做货&q…

作者头像 李华
网站建设 2026/7/2 4:38:03

酷狗音乐API签名失效排查与修复实战

1. 项目概述&#xff1a;当音乐API签名失效时如果你正在使用或维护一个基于KuGouMusicApi的音乐服务项目&#xff0c;那么“签名错误”这四个字&#xff0c;很可能就是你深夜调试时最不想看到的报错信息。这不仅仅是一个简单的400或403状态码&#xff0c;它背后往往意味着整个数…

作者头像 李华