专栏名称: 芋道源码
纯 Java 源码分享公众号,目前有「Dubbo」「SpringCloud」「Java 并发」「RocketMQ」「Sharding-JDBC」「MyCAT」「Elastic-Job」「SkyWalking」「Spring」等等
目录
相关文章推荐
Java编程精选  ·  雷军删文,热搜第一! ·  2 天前  
芋道源码  ·  如何实现一个合格的分布式锁 ·  昨天  
芋道源码  ·  再见了SpringBoot,后端AI已成气候! ·  2 天前  
51好读  ›  专栏  ›  芋道源码

如何实现一个合格的分布式锁

芋道源码  · 公众号  · Java  · 2025-05-22 09:29

正文

请到「今天看啥」查看全文



1、概述

在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用 synchronized 语法和 ReentrantLock 去保证,这实际上是本地锁的方式。而在如今分布式架构的热潮下,如何保证不同节点的线程同步执行呢?

实际上,对于分布式场景,我们可以使用分布式锁,分布式锁是用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用。

分布式锁的特点

  • 「互斥性:」 同一时刻只能有一个线程持有锁。
  • 「可重入性:」 同一节点上的同一个线程如果获取了锁之后能够再次获取锁。
  • 「锁超时:」 类似于J.U.C中的锁,支持锁超时,以防止死锁。
  • 「高性能和高可用:」 加锁和解锁需要高效,并且需要保证高可用性,防止分布式锁失效。
  • 「具备阻塞和非阻塞性:」 能够及时从阻塞状态中被唤醒。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

2、Redis粗糙实现

Redis本身可以被多个客户端共享访问,是一个共享存储系统,适合用来保存分布式锁。由于Redis的读写性能高,可以应对高并发的锁操作场景。

Redis的 SET 命令有一个 NX 参数,可以实现「key不存在才插入」,因此可以用它来实现分布式锁:

  1. 如果key不存在,则表示插入成功,可以用来表示加锁成功;
  2. 如果key存在,则表示插入失败,可以用来表示加锁失败;
  3. 当需要解锁时,只需删除对应的key即可解锁成功;
  4. 为了避免死锁,需要设置合适的过期时间。

这样描述,我们可以得到一个十分粗糙的分布式锁实现。

// 尝试获得锁 
if (setnx(key, 1) == 1){  
// 获得锁成功,设置过期时间   
expire(key, 30
try {        
    //TODO 业务逻辑 

finally {        
    // 解锁       
    del(key)  
  }
}

然而,上述实现方式存在一些问题,使其不能被称为合格的分布式锁:

  1. 「非原子性操作:」 多条命令的操作不是原子性的,可能会导致死锁的产生。
  2. 「锁误解除:」 存在锁误解除的可能性,即在持有锁的线程在内部出现阻塞时,锁的TTL到期导致自动释放,而其他线程误解除锁的情况。
  3. 「业务超时自动解锁导致并发问题:」 由于业务超时自动解锁,可能导致并发问题的发生。
  4. 「分布式锁不可重入:」 实现的分布式锁不支持重入。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

3、解决遗留问题

3.1误删情况

在以下情况下可能会出现误删情况:

  • 持有锁的线程1在锁的内部出现了阻塞,导致其锁的TTL到期从而锁自动释放;
  • 此时线程2尝试获取锁,由于线程1已经释放了锁,线程2可以拿到;
  • 但是随后线程1解除阻塞,继续执行并开始释放锁;






请到「今天看啥」查看全文