尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

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

【微服务学习笔记】分布式锁与线程锁的理解和使用
📅 发布时间:2026/7/2 4:51:45

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

一、线程锁(本地锁,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)如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); // 获取所有子节点并排序 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. 线程锁解决单机多线程竞争,分布式锁解决多服务实例竞争;乐观锁适合读多写少,悲观锁适合写多读少;读写锁提升读并发,信号量实现限流。锁的本质是"串行化临界资源访问",需根据场景选择合适粒度。

相关新闻

  • 洗牙并非简单清洁:规范洁牙科普指南
  • LabVIEW让故障排查从“猜“变“算“
  • AI 写了 500 行代码,上线后发现漏了 3 个接口、2 个路由、1 个菜单 —— 这套方法论让这种事再也没发生过

最新新闻

  • GreaterWMS仓库管理系统:从零到一的5步部署实战指南
  • Windows系统设置自动登录的方式
  • 拒绝盲目踩坑!6款经过市场验证的高性价比新手吉他推荐
  • 工程费用--项目的“血肉骨架”
  • npm依赖漏洞修复提速3倍:Snyk集成CI安全扫描的4步落地实践
  • 打完井能不能帮忙做水质检测,保障用水健康?

日新闻

  • Python Playwright录制功能:从零到一构建自动化测试脚本
  • 如何用开源工具永久保存你心爱的小说:novel-downloader全攻略
  • In-Context Learning不是教知识,而是模式对齐:从5个示例到100个工业级样本的真相

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号