分布式锁的基本原理和实现以及synchronized底层原理

news2025/2/26 15:08:29

1.1Synchronized 

Synchronized的重点级锁,底层是基于锁监督器(Monitor)来实现,简单来说就是锁对象头会指向一个锁监督器,而在监督器中则会记录一些信息,比如:

  • _owner:持有锁的线程
  • _recursions:锁重入次数

因此每一个锁对象,都会指向一个锁监视器,而每一个锁监视器,同一时刻只能被一个线程持有,这样就实现了互斥效果。但前提是,多个线程使用的是同一把锁

比如有三个线程来争抢锁资源,线程1获取锁成功,如图所示:

此时其它线程想要获取锁,会发现监视器中的_owner已经有值了,就会获取锁失败。由于咱们代码在锁对象是用户id的字符串常量,因此同一个用户肯定是同一把锁,线程是绝对安全的。

但问题来了,我们的服务将来肯定会多实例不是,形成集群。每一个实例都会有一个自己的JVM运行环境,因此即便是同一个用户,如果并发的发起了多个请求,由于请求进入了多个JVM,就会出现多个锁对象(用户id对象),自然就有多个锁监视器。此时就会出现每个JVM内部都有一个线程获取锁成功的情况,没有达到互斥的效果,并发安全问题就可能再次发生了:

 可见,在集群环境下,JVM提供的传统锁机制就不再安全了。

那么改如果解决这个问题呢?

显然,我们不能让每个实例去使用各自的JVM内部锁监视器,而是应该在多个实例外部寻找一个锁监视器,多个实例争抢同一把锁

像这样的锁,就称为分布式锁

分布式锁必须要满足的特征:

  • 多JVM实例都可以访问

  • 互斥

能满足上述特征的组件有很多,因此实现分布式锁的方式也非常多,例如:

  • 基于MySQL

  • 基于Redis

  • 基于Zookeeper

  • 基于ETCD

但目前使用最广泛的还应该是基于Redis的分布式锁。

1.2.简单分布式锁

Redis本身可以被任意JVM实例访问,同时Redis中的setnx命令具备互斥性,因此符合分布式锁的需求。不过实现分布式锁的时候还有一些细节需要考虑,绝不仅仅是setnx这么简单。

 

1.2.1.基本原理

Redis的setnx命令是对string类型数据的操作,语法如下:

# 给key赋值为value
SETNX key value

当前仅当key不存在的时候,setnx才能执行成功,并且返回1,其它情况都会执行失败,并且返回0.我们就可以认为返回值是1就是获取锁成功,返回值是0就是获取锁失败,实现互斥效果。

而当业务执行完成时,我们只需要删除这个key即可释放锁。这个时候其它线程又可以再次获取锁(执行setnx成功)了。

# 删除指定key,用来释放锁
DEL key

假设说一开始lock不存在,有很多线程同时对lock执行setnx命令。由于Redis命令本身是串行执行的,也就是各个线程是串行依次执行。因此当第一个线程执行setnx时,会成功添加这个lock。但其余的线程会发现lock已经存在,自然就执行失败。自然就实现了互斥效果。

当业务执行完毕,直接删除lock,自然就释放锁了

# 释放锁
DEL lock

不过我们要考虑一种极端情况,比如我们获取锁成功,还未释放锁呢当前实例突然宕机了!那么释放锁的逻辑自然就永远不会被执行,这样lock就永远存在,再也不会有其它线程获取锁成功了!出现了死锁问题。

怎么办?

我们可以利用Redis的KEY过期时间机制,在获取锁时给锁添加一个超时时间:

# 获取锁,并记录持有锁的线程
SETNX lock thread1
# 设置过期时间,避免死锁
EXPIRE lock 20

令来释放锁。

但是如果当前服务实例宕机,DEL无法执行。但由于我们设置了20秒的过期时间,当超过这个时间时,锁会因为过期被删除,因此就等于释放锁了,从而避免了死锁问题。这种策略就是超时释放锁策略。

但新的问题来了,SETNX和EXPIRE是两条命令,如果我执行完SETNX,还没来得急执行EXPIRE时服务已经宕机了,这样加锁成功,但锁超时时间依然没能设置!死锁问题岂不是再次发生了?!

所以,为了解决这个问题,我们必须保证SETNX和EXPIRE两个操作的原子性。事实上,Redis中的set命令就能同时实现setnx和expire的效果:

# NX 等同于SETNX lock thread1效果;
# EX 20 等同于 EXPIRE lock 20效果
SET lock thread1 NX EX 20

综上,利用Redis实现的简单分布式锁流程如下:

1.2.2代码实现

 

package com.tianji.promotion.utils;

import com.tianji.common.utils.BooleanUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

@RequiredArgsConstructor
public class RedisLock {

    private final String key;
    private final StringRedisTemplate redisTemplate;

    /**
     * 尝试获取锁
     * @param leaseTime 锁自动释放时间
     * @param unit 时间单位
     * @return 是否获取成功,true:获取锁成功;false:获取锁失败
     */
    public boolean tryLock(long leaseTime, TimeUnit unit){
        // 1.获取线程名称
        String value = Thread.currentThread().getName();
        // 2.获取锁
        Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, leaseTime, unit);
        // 3.返回结果
        return BooleanUtils.isTrue(success);
    }

    /**
     * 释放锁
     */
    public void unlock(){
        redisTemplate.delete(key);
    }
}

 经测试,确实解决了集群下的并发安全问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1126623.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动驾驶之—车道线感知

零、前言 : 最近在学习自动驾驶方向的东西,简单整理一些学习笔记,学习过程中发现宝藏up 手写AI 一、视觉系统坐标系 视觉系统一共有四个坐标系:像素平面坐标系(u,v)、图像坐标系(x,y&#xff09…

华泰证券:达达集团(DADA)3Q23业绩前瞻:短期业绩承压

来源:猛兽财经 作者:猛兽财经 猛兽财经获悉,考虑到宏观实物消费恢复相对较弱,华泰证券发布关于达达集团(DADA)达达集团(DADA)3Q23业绩前瞻:短期业绩承压的研报。 华泰证券在研报中预…

【Linux】【驱动】设备树中设备节点的挂载

【Linux】【驱动】设备树中设备节点的挂载 代码操作脚本Linux中的操作下位机中的操作指令 代码 设备树对应的文件是100ask_imx6ull_mini.dtb 所以需要在根节点上增加相关的测试代码 我们修改的就是hi如下的代码部分 增加测试节点 test1:test1{#addrsee-cells < 1 >;#s…

C算法:递归算法求a的n次方

需求&#xff1a; 用递归算法写一个函数&#xff0c;实现a的n次方。 代码实现&#xff1a; #include <stdio.h> #include <stdlib.h> int nndata(int a,int n) {if(n<1){printf("please input numdata(>1) !\n");exit(-1);}if(n1){return a;}els…

基于Java的校园办公室报修管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

写一个JAVA逻辑的手动计算方法

逻辑&的手动实现 直接上代码吧计算结果 直接上代码吧 public class TestStream1023 {Testpublic void testPareCom() {String res testPareCom(12, 5, 8);System.out.println("逻辑&计算的结果二进制:["res"]");System.out.println("二级制…

Ubuntu22.04安装,SSH无法连接

Ubuntu初始化安装后&#xff0c;系统默认不允许root通过ssh连接&#xff0c;因此需要完成三个设置 1.修改ssh配置文件 vim /etc/ssh/sshd_config 将PermitRootLogin注释打开&#xff0c;并将值改为yes 保存修改并退出 :wq 2.重启ssh服务 sudo service ssh restart 3.重新打…

智加科技多项成果亮相ITS World Congress 两款智能重卡计划量产

2023年10月16日-20日&#xff0c;第29届智能交通世界大会&#xff08;ITS World Congress&#xff09;在苏州国际博览中心成功举办。智能交通世界大会被誉为智能交通领域的“奥运会”&#xff0c;是智能交通领域最具影响力的综合性国际会议&#xff0c;也是智能交通业界规格最高…

蒙特卡洛树搜索(MCTS)怎么实现的?+ 上置信范围Upper Confidence Bounds(UCB)是什么?

一、算法思想 我们回想一下我们下棋时的思维——并没有在脑海里面把所有可能列出来&#xff0c;而是根据「棋感」在脑海里大致筛选出了几种「最可能」的走法&#xff0c;然后再想走了这几种走法之后对手「最可能」的走法&#xff0c;然后再想自己接下来「最可能」的走法&…

第一讲之递推与递归上篇

第一讲之递推与递归上篇 数据与算法的关系简单斐波那契递归实现指数型枚举递归实现排列型枚举递归实现组合型枚举 本专栏博客&#xff0c;根据acwing中蓝桥杯CAB组辅导课编写 数据与算法的关系 简单斐波那契 简单斐波那契 斐波那契数列的话&#xff0c;只要掌握规律&#xff0…

Danielle Foré 近日向 9to5Linux 通报了 elementary OS 7.1 的发布和全面可用性

导读Danielle For 近日向 9to5Linux 通报了 elementary OS 7.1 的发布和全面可用性&#xff0c;这是自 elementary OS 7.0 “Horus “于 2023 年 1 月发布以来&#xff0c;基于 Ubuntu 的发行版的首次重大更新。 elementary OS 7.1 引入了新的隐私功能&#xff0c;包括系统设置…

RK3568 蓝牙测试

本个章节所使用的蓝牙为E104-BT5032A,这款蓝牙芯片为免驱,只需要uart正常即可使用,理论上除了3568以外都可以使用 E104-BT5032A有几个地方需要注意,首先是他有几个管脚需要配置的,经常没去留意着三个引脚的电平配置,导致使用异常,然后就是保证uart能够正常使用即可 首先…

前端git提交后菜单的配置

1.添加元数据–》app微服务 在系统中找到自己对应的位置&#xff0c;然后点击进去找到功能用例&#xff0c;添加相应的前端路由和中文名称 2.在权限管理中的菜单管理中添加相应的菜单权限

SQL关于日期的计算合集

前言 在SQL Server中&#xff0c;时间和日期是常见的数据类型&#xff0c;也是数据处理中重要的一部分。SQL Server提供了许多内置函数&#xff0c;用于处理时间和日期数据类型。这些函数可以帮助我们执行各种常见的任务&#xff0c;例如从日期中提取特定的部分&#xff0c;计…

Photoshop 2024(ps ai beta) v25.0

Photoshop 2024是一款业界领先的图像编辑软件&#xff0c;被广泛应用于设计、摄影、插图等领域。以下是这款软件的一些主要功能和特点&#xff1a; 丰富的工具和功能。Adobe Photoshop 2024提供了丰富的工具和功能&#xff0c;可以帮助用户对图像进行编辑、修饰和优化。它支持…

Linux 挂载磁盘到指定目录

问题&#xff1a;公司分配了数据磁盘&#xff0c;但是分区也没有挂载到目录 首先 df -h 查看一下挂载点的情况 查看服务器上未挂载的磁盘 fdisk -l 注&#xff1a;图中sda、sdb &#xff08;a、b指的是硬盘的序号&#xff09; 分区操作 我们可以看到b硬盘有536G未分区&…

【Java】智慧工地云SaaS源码,AI服务器、硬件设备讲解视频

智慧工地是指运用信息化手段&#xff0c;通过三维设计平台对工程项目进行精确设计和施工模拟&#xff0c;围绕施工过程管理&#xff0c;建立互联协同、智能生产、科学管理的施工项目信息化生态圈&#xff0c;并将此数据在虚拟现实环境下与物联网采集到的工程信息进行数据挖掘分…

JVM——一些零散的概念(后续学习深入了再补充)

Native 凡是带了native关键字的&#xff0c;说明Java的作用范围的达不到了&#xff0c;需要调用底层C语言的库 调用native方法&#xff0c;会进入本地方法栈&#xff0c;调用本地接口(JNI) JNI的作用&#xff1a;扩展Java的使用&#xff0c;融合不同的编程语言为Java所用 它在内…

Proteus仿真--一种智能频率计的设计与制作(AVR单片机+proteus仿真)

本文介绍一种基于AVR单片机实现的一种智能频率计Proteus仿真实现&#xff08;完整仿真源文件及代码见文末链接&#xff09; 简介 硬件电路主要分为单片机主控模块、频率计模块、LCD1602液晶显示模块以及串口模块 &#xff08;1&#xff09;单片机主控模块&#xff1a;单片机…

nginx详细安装教程

一、简介 1-1 什么是 nginx Nginx 是高性能的 HTTP 和反向代理的web服务器&#xff0c;处理高并发能力是十分强大的。 Nginx 可以作为静态页面的 web 服务器&#xff0c;同时还支持 CGI 协议的动态语言&#xff0c;比如 perl、php 等。但是不支持 java。Java 程序只能通过与 …