SpringBoot实现分布式锁

article/2025/9/14 8:12:12

SpringBoot实现分布式锁

先了解一下线程数:

线程锁

线程锁:主要用来给类,方法,代码加锁,当某个方法或者某块代码使用synchronize关键字来修饰,那么在同一时刻最多只能有一个线程执行该代码,如果同一时刻有多个线程访问该代码,其它未抢到资源的线程标记为阻塞状态,直到获取到锁的线程执行完毕自动释放其余线程才能执行
线程锁synchronize在单机部署的状态下,确实可以保证线程安全,但是如果是集群或者分布式部署呢?

集群模式下线程锁为何无法满足锁需求?

分布式的CAP理论

任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项

  • 现在许多项目考虑到性能和扩展性都通过分布式的方式进行部署,分布式中的数据一致性一直是一个比较重要的问题,基于CAP的理论,在系统最开始设计的时候,就需要对这三点进行取舍,在我们的场景中,一般来讲都是牺牲强一致性,来提升高可用,系统只需要保证最终一致性即可
  • 如果要保证数据的最终一致性可以通过分布式锁,分布式事务等一些技术来实现,很多时候要达到最终一致性,也要保证一个方法在同一时刻只能被一个线程执行
  • 上面我们也说了,在单机环境中,synchronize也确实可以解决我们的问题,但是在分布式环境中,就不可以了,为什么?
  • 我们要知道,分布式系统和单机系统最大的区别就是,单机系统中是多线程,分布式系统中是多进程
    多线程可以共享一个Jvm中的堆内存,所以可以采取内存作为标记存储的位置,多进程的情况下,有可能这些进程都不在同一台物理机上,是无法共享同一Jvm中的堆内存,所以就需要把标记存储在一个第三方上面,保证所有进程可见
  • 讲到这里大家也大概能反推出synchronize的底层实现了吧,其实就是Jvm在方法常量池中的方法表结构访问标志区来判断某个方法是否同步方法,方法被调用的时候,调用的指令就会检查方法的访问标记有没有被设置同步,如果设置了,当前执行线程就会持有一个标记,然后执行方法,最后执行完再释放这个标记

设计分布式锁

  • 通过上面的了解,大家应该可以再次反推出分布式锁的实现了吧
  • 在实现分布式锁之前,我们先设计下分布式锁的实现

我们需要什么样的分布式锁?

(我说一下如果是我们场景下的设计)

  • 首先性能肯定要好,获取锁和释放锁的性能一定要高
  • 阻塞锁就可以满足我们的需求(还有非阻塞,公平锁等等各种方式)
  • 不能出现死锁的情况
  • 然后基于以上的几点,我们来开发分布式锁

实现

使用Redis实现分布式锁

增加Redis的Pom文件

   <!--Redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- redis依赖commons-pool 这个依赖一定要添加 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>
  • 我们使用Redis的setnx(Set If Not Exists)

    • setnx的解释:如果指定的key不存在则写入

    • 在RedisClient中setnx写入成功返回1,否则返回0

    • 在JavaRedisApi中写入成功返回True,否则返回False

    • 可以成功写入代表当前的方法并没有被其它进程占用 写入失败代表当前的方法正在被其它进程占用

    • Redis本身是单线程IO多路复用技术,不存在线程安全的问题,所以不用考虑setInx本身线程安全的问题

  • 了解了setnx的用法后,我们的思路就是:

    • 获取锁:使用setnx在Redis中标记当前方法正在被其它进程占用(指定Key) 释放锁:删除我们在Redis中的标记(指定Key)
    • 避免死锁:如果极端情况下,获取锁后执行方法异常导致服务挂了,那么锁是不会释放的,有可能会死锁,所以在获取锁后,需要设置过期时间,防止死锁
      如下所示:
      在这里插入图片描述

创建RedisUtils工具类

package com.util;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import v1.exceptionding.DingCloverExceptionEnum;
import v1.exceptionding.DingException;
import v1.util.ToolUtil;import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;@Component
public class RedisUtils {@Autowiredprivate StringRedisTemplate redisTemplate;/*** Redis分布式锁** @return*/public boolean tryLock(String key, String value, long timeout) {if (timeout == 0) {timeout = 60 * 3;}boolean isSuccess = redisTemplate.opsForValue().setIfAbsent(key, value);//设置过期时间,防止死锁if (isSuccess) {redisTemplate.expire(key, timeout, TimeUnit.SECONDS);}return isSuccess;}/*** Redis 分布式锁释放** @param key* @param value*/public void unLock(String key, String value) {try {String currentValue = redisTemplate.opsForValue().get(key);if (ToolUtil.isNotEmpty(currentValue) && ToolUtil.equals(currentValue, value)) {redisTemplate.opsForValue().getOperations().delete(key);}} catch (Exception e) {//这个是我的自定义异常,你可以删了throw new MyException(DingCloverExceptionEnum.InternalServerError);}}
}

获取锁和释放锁的工具写好后,定义我们的业务代码

public void mockLock() {RedisUtils redisUtils = SpringContextHolder.getBean(RedisUtils.class);InetAddress addr = null;try {addr = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}//获取本机ipString ip = addr.getHostAddress();//此key存放的值为任务执行的ip,// expire_time 不能设置为永久,避免死锁boolean lock = redisUtils.tryLock("lock_key", ip, 0);if (lock) {System.out.println("获取分布式锁成功");run();//释放锁redisUtils.unLock("lock_key",ip);System.out.println("释放分布式锁成功");} else {System.out.println("获得分布式锁失败");ip = (String) redisUtils.get(lock_key);System.out.println(ip+"正在执行该任务");return;}
}public void run() throws InterruptedException {System.out.println("业务执行中");Thread.sleep(60 * 3);System.out.println("业务执行结束");
}

转载https://blog.csdn.net/youbitch1/article/details/103906249


http://chatgpt.dhexx.cn/article/28MQZOEx.shtml

相关文章

深入理解ConcurrentHashMap原理分析以及线程安全性问题

在之前的文章提到ConcurrentHashMap是一个线程安全的&#xff0c;那么我么看一下ConcurrentHashMap如何进行操作的。 ConcurrentHashMap与HashTable区别&#xff1f; HashTable put()源代码 我们来看一下put 操作&#xff1a; 方法体 被 同步锁标记&#xff0c;由于同步锁的…

Redis分布式锁到底安全吗?

若有收获,请记得分享和转发哦 这篇文章我想和你聊一聊&#xff0c;关于 Redis 分布式锁的「安全性」问题。 Redis 分布式锁的话题&#xff0c;很多文章已经写烂了&#xff0c;我为什么还要写这篇文章呢&#xff1f; 因为我发现网上 99% 的文章&#xff0c;并没有把这个问题真正…

Java线程安全

前段时间有测试一个后端对账单和话单采集服务,在测试过程中有涉及到数据库读写逻辑和并发的场景,所以结合经验针对系统技术架构设计了部分并发场景结合数据库读写时可能出现的一些问题的用例,也确实出现了一些测试环境容易忽视,线上环境确确实实可能出现的问题,当然最后还是得到…

线程安全之 - ThreadLocal

ThreadLocal的底层原理 ThreadLocal是Java中所提供的线程本地存储机制&#xff08;线程内共享&#xff09;&#xff0c;可以利⽤该机制将数据缓存在某个线程内部&#xff0c; 该线程可以在任意时刻、任意⽅法中获取缓存的数据&#xff1b;ThreadLocal底层是通过ThreadLocalMap…

线程锁(ReentrantLock、synchronized)为何不能用作分布式锁

为什么使用分布式锁 分布式锁实现目前有三种&#xff1a; 数据库乐观锁&#xff1b;ZooKeeper的分布式锁;Redis的分布式锁&#xff1b; 在以前单体架构Web应用场景下&#xff0c;我们可以使用ReentrantLock或synchronized进行上锁&#xff0c;保证资源安全&#xff0c;现如今大…

Redis分布式锁真的安全吗?

大家好&#xff0c;今天我们来聊一聊Redis分布式锁。 首先大家可以先思考一个简单的问题&#xff0c;为什么要使用分布式锁&#xff1f;普通的jvm锁为什么不可以&#xff1f; 这个时候&#xff0c;大家肯定会吧啦吧啦想到一堆&#xff0c;例如java应用属于进程级&#xff0c;…

ThreadLocal能解决线程安全问题?胡扯!本文教你正确的使用姿势【享学Java】

跟对领导很重要&#xff1a;愿意教你的&#xff0c;并且放手让你做的领导要珍惜。 目录 前言正文ThreadLocal是什么&#xff1f;ThreadLocal怎么用&#xff1f;局限性InheritableThreadLocal向子线程传递数据开源框架使用示例 ThreadLocal不能解决共享变量的线程安全问题Thread…

Java线程安全详细总结

以下是我的PPT文档&#xff0c;不知道怎么复制到博客&#xff0c;只能一个一个插入图片发上来了。感觉总结的不错&#xff0c;分享一下。 文档地址&#xff1a;http://download.csdn.net/detail/csujiangyu/9526641

分布式系统详解--基础知识(线程)

分布式系统详解--基础知识&#xff08;线程&#xff09; 一、导读 前面跟大家讲了一下 分布式系统详解--基础知识&#xff08;概论&#xff09; &#xff0c;可以稍微了解一下大体上分布式是怎么一回事了。这片篇文章主要是讲述一下线程的问题分别介绍一下&#xff0c;什么线…

分布式项目线程安全问题(电商扣减库存的安全问题1)

电商减库存存在的安全问题 Override public void deductStock(Map<Long, Integer> skuMap) {for (Map.Entry<Long, Integer> entry : skuMap.entrySet()) {Long skuId entry.getKey();Integer num entry.getValue();// 查询skuSku sku getById(skuId);// 判断…

分布式项目中 如何保证线程安全问题?-------ZooKeeper

前沿&#xff1a; 上篇文章我们聊到了在解决分布式项目中线程安全问题&#xff0c;提到解决方案还有其他的&#xff0c;那么在此提出 基于 zookeeper 解决分布式项目中的线程安全问题 也是目前市面上比较流行的。做为一个高级开发工程师也是必须要学习的。 ZooKeeper是什么东…

分布式线程安全(redis、zookeeper、数据库)

https://blog.csdn.net/u010963948/article/details/79006572 Q:一个业务服务器&#xff0c;一个数据库&#xff0c;操作&#xff1a;查询用户当前余额&#xff0c;扣除当前余额的3%作为手续费 synchronized lock db lock Q&#xff1a;两个业务服务器&#xff0c;一个数据库&…

分布式集群中如何保证线程安全?

目录 分布式集群中的线程安全问题 解决方法 串行化 分布式锁 Redis如何实现呢&#xff1f; 问题&#xff1a;setnx刚好获取到锁&#xff0c;业务逻辑出现异常&#xff0c;导致锁无法释放 问题&#xff1a;可能会释放其他服务器的锁。 问题&#xff1a;删除操作缺乏原子…

java outlook 发送邮件_基于java使用JavaMail发送邮件

一、邮件的相关概念 邮件协议。主要包括&#xff1a; SMTP协议&#xff1a;Simple Mail Transfer Protocol&#xff0c;即简单邮件传输协议&#xff0c;用于发送电子邮件 POP3协议&#xff1a;Post Office Protocol 3&#xff0c;即邮局协议的第三个版本&#xff0c;用于接收邮…

java 发邮件(有正文,有图片,有附件)

一 需求: 1 java实现邮件发送 2 发送内容: ① 正文: 图片说明和图片 ② 附件一: 图片作为附件发送 ③ 附件二: Excel表格 二 思路: 1首先创建一个 Java 工程&#xff0c;把下载好的 javax.mail.jar 作为类库加入工程 2邮件创建步骤: 配置连接邮件服务器的参数( 邮件服务器SM…

java接收邮件_Java实现邮件收发

一. 准备工作 1. 传输协议 SMTP协议-->发送邮件: 我们通常把处理用户smtp请求(邮件发送请求)的服务器称之为SMTP服务器(邮件发送服务器) POP3协议-->接收邮件: 我们通常把处理用户pop3请求(邮件接收请求)的服务器称之为POP3服务器(邮件接收服务器) 2. 邮件收发原理 闪电…

java发送邮件工具类

1. 普通java实现邮件发送 1.1 创建maven项目&#xff0c;配置pom.xml文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&qu…

java发送邮件带附件

一、 开启SMTP服务 1.基本都在邮箱设置里&#xff0c;开启后会获得神秘代码&#xff0c;后面有用。 2.记得添加依赖&#xff0c;或者自己添加jar包。 <dependency><groupId>javax.mail</groupId><artifactId>mail</artifactId><version>…

java 邮件模板

邮件发送代码可参照 java 发送邮件 1.情形 邮件发送代码可参照上述&#xff0c;本例只说明如果读取模板文件。公司定义模板较为复杂的情况&#xff0c;可采用此类发送方式 2. 模板 2.1 resource 建立模板 2.2 ftl 模板如下 <p>您好&#xff0c;${name}&#xff0c;您…

使用JAVA实现邮件发送功能

一、准备工作 小编今天以 QQ邮箱 进行演示操作。 想要使用代码操作邮箱发送邮件&#xff0c;需要在邮箱设置中申请开通 POP3/SMTP 服务。 接下来跟着小编的图文一步一步的操作开通吧&#xff01; 1.1 登录网页QQ邮箱&#xff0c;点击页面顶部设置按钮。 1.2 点击后会打开邮箱…