iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >SETNX分布锁详解
  • 559
分享到

SETNX分布锁详解

jvmjava开发语言redis1024程序员节 2023-09-03 06:09:07 559人浏览 薄情痞子
摘要

前提 为何要使用分布式锁? 其实使用分布式锁的目的与平常使用synchronized锁,Lock锁的目的一致,就是为了确保多线程并发时,在某些业务场景中让这些线程串行执行。 通常在一个JVM中让线程串

前提

为何要使用分布式锁?
其实使用分布式的目的与平常使用synchronized锁,Lock锁的目的一致,就是为了确保多线程并发时,在某些业务场景中让这些线程串行执行。
通常在一个JVM中让线程串行执行是比较容易实现的,例如synchronized锁,Lock锁等。但是现在为了应对更大的并发量,通常会将服务拆分或者搭建集群,此时synchronized锁,Lock锁就无法让处于不同服务器中的线程互斥,或者说让处于不同JVM中的线程互斥。此时就需要使用分布式锁让这些不同服务中的线程互斥,从而串行执行。
集群下锁失效
那什么是分布式锁?
分布式锁:在分布式系统下或者集群模式下能够实现多个线程之间互斥并且可见。
分布式锁的思想就是让处于不同服务器中的线程共同使用同一把锁,从而保证这些线程在某些场景下串行执行
分布式锁有那些?
说到分布式锁其实还是有很多的,像Mysql分布式锁,Redis分布式锁,ZooKeeper分布式锁等
现在我们采用Redis作为分布式锁,那么将上述图片的中的两个监视器换为一个redis在外部,那么图片就形象生动的出现在脑海中,脑补

SETNX命令的特性

SETNX:向Redis中添加一个key,只用当key不存在的时候才添加并返回1,存在则不添加返回0。并且这个命令是原子性的。
使用SETNX作为分布式锁时,添加成功表示获取到锁,添加失败表示未获取到锁。至于添加的value值无所谓可以是任意值(根据业务需求),只要保证多个线程使用的是同一个key,所以多个线程添加时只会有一个线程添加成功,就只会有一个线程能够获取到锁。而释放锁锁只需要将锁删除即可。
总结:

  1. 获取锁:通过setnx添加
  2. 释放锁:通过del将锁删除

SETNX分布式锁

设置过期时间防止死锁

假设线程1通过SETNX获取到锁并且正常执行然后释放锁那么一切ok,其它线程也能获取到锁。但是线程1现在"耍脾气"了,线程1抱怨说"工作太久有点累需要休息一下,你们想要获取锁等着吧,等我把活干完你们再来获取锁"。此时其它线程就无法向下继续执行,因为锁在线程1手中。这种长期不释放锁情况就有可能造成死锁
为了防止像线程1这种"耍脾气"的现象发生,我们可以设置key的过期时间来解决。设置过期时间过后其它线程可不会惯着线程1,其它线程表示你要休息可以,休息了指定时间把锁让出来然后拍拍屁股走人,没人惯着你。
Redis命令:

在添加时存在则添加,不存在则不添加。同时设置过期时间,单位秒SET key value NX EX time原命令及其参数   SET key value [NX | XX] [GET] [EX seconds | PX milliseconds |  EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]

JAVA代码:

//通过java代码实现SETNX同时设置过期时间//key--键   value--值  time--过期时间  TimeUnit--时间单位枚举stringRedisTemplate.opsForValue().setIfAbsent(key, value , time, TimeUnit);

SETNX分布式锁时误删情况

情况一
设置过期时间线程1被治得服服帖帖,此时线程1又开始不当人了。线程1想既然你抢我得锁,等你获得锁后我就将锁删除毕竟我还要有备用钥匙,让你也锁不住,让其它线程也执行
线程1休息的时间超过了过期时间,此时锁会自动释放。线程2现在脱颖而出抢到了锁然后开心的继续执行。但是现在线程1醒了,发现线程2抢走了锁。线程1表示小子胆挺肥啊,敢抢我的锁,等我执行完了就将你锁删除,让其它"哥们"也进来。此时就会发生蝴蝶效应,线程1删除了线程2的锁,线程2删除了线程3的锁,直到最后一个"哥们:wc,我锁了?"。当然线程是无感知,其实线程1乃至其它线程都不知道删除的是别人的锁,全部线程都以为删除的是自己的锁。直到最后一个线程无锁可删。
这种误删锁的情况让锁的存在荡然无存,本来应该串行执行的线程,在一定程度上都开始并发执行了。
那么误删情况该如何解决了?
我们可以给锁加上线程标识,只有锁是当前线程的才能删除,否则不能删除。在添加key的时候,key的value存储当前线程的标识,这个标识只要保证唯一即可。可以使用UUID或者一个自增数据。在删除锁的时候,将线程标识取出来进行判断,如果相同就表示锁是自己的能够删除,否则不能删除。

获取锁:

//获取线程前缀,同时也是线程表示。通过UUID唯一性private static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";//与线程id组合public boolean tryLock(long timeOut) {        //获取线程id        String id =ID_PREFIX+ Thread.currentThread().getId();        //获取锁        Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(key, id , timeOut, TimeUnit.SECONDS);        return Boolean.TRUE.equals(absent);    }

释放锁:

public void unLock() {//获取存储的线程标识        String value = stringRedisTemplate.opsForValue().get(key);        //当前线程的线程标识        String id =ID_PREFIX+ Thread.currentThread().getId();        //线程标识相同则删除否,则不删除        if (id.equals(value)){            stringRedisTemplate.delete(key);        }    }

情况二
加入线程标识后,线程一不能随便删除其它线程的锁,但是线程1又开始不当人了。线程1表示判断线程标识和释放锁的操作我可以分开执行,这又不是一个原子性的操作,线程1干完活以后就准备去释放锁,当线程1判断锁是自己的后表示开锁太累了,休息一会在开。此时其它线程就想无所谓,反正过期时间一到锁就会自动释放。但是线程1已经判断了锁是自己的以后就不会执行判断锁的操作(线程1已经执行了if判断,只是没有执行方法体),当线程2获得锁后,线程1仍然能删除线程2的锁。
特别说明:

  1. 线程1执行的时间片到了就会发生线程切换
  2. JVM执行垃圾回收,因为垃圾回收会暂停用户线程,让垃圾回收线程单独执行。尽管现在的CMS,G1垃圾回收器能够做到并发收集,但是CMS和G1的初始标记以及CMS的重新标记,G1的最终标记仍然需要STW(Stop The World)
public void unLock() {        String value = stringRedisTemplate.opsForValue().get(key);        //在此处发生垃圾回收,线程1暂停并且超过了过期时间,线程2先获得了锁        String id =ID_PREFIX+ Thread.currentThread().getId();        if (id.equals(value)){        //在此处发生垃圾回收,线程1暂停并且超过了过期时间,线程2先获得锁            stringRedisTemplate.delete(key);        }    }

此时就需要将判断锁的操作和删除锁的操作作为一个整体执行,要么全部成功,要么全部失败,保证删除锁的原子性。

lua脚本解决多条命令原子性问题

关于Lua语言参考此链接:Lua
导致线程1删除线程2锁的原因就是判断锁和删除锁的操作不是原子性的。此时可以使用Lua脚本保证多条命令的原子性。
Redis提供的函数:

redis.call('命令名称', 'key', '其它参数', ...)例如:redis.call('set','name','jack')---->set name jack

Lua脚本: 会将key存储在KEYS数组中,会将value存储在ARVG数组中。下标从1开始

if (redis.call('get',KEYS[1])==ARGV[1]) then    return redis.call('DEL',KEYS[1])end    return 0

JAVA:通过execute()方法执行Lua脚本,DefaultRedisScript加载脚本

private static final DefaultRedisScript<Long> DEFAULT_REDIS_SCRIPT;    static {        DEFAULT_REDIS_SCRIPT=new DefaultRedisScript<Long>();        DEFAULT_REDIS_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));        DEFAULT_REDIS_SCRIPT.setResultType(Long.class);    }     public void unLock() {        stringRedisTemplate.execute(DEFAULT_REDIS_SCRIPT, Collections.singletonList(RedisConstants.KEY_PREFIX + name),ID_PREFIX+ Thread.currentThread().getId());    }

最后

文中提及SETNX作为分布式锁的原理以及存在的问题都是学习过程中的总结
SETNX分布锁锁存在的问题:

  1. 重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是使用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。
  2. 不可重试:是指目前的分布式只能尝试一次,合理的情况是:当线程在获得锁失败后,应该能再次尝试获得锁。
  3. 超时释放:加锁时增加了过期时间,可以防止死锁,但是如果卡顿的时间超长,虽然采用了lua表达式防止删锁的时候,误删别人的锁,但是毕竟没有锁住,有安全隐患
  4. 主从一致性: 如果Redis提供了主从集群,当向集群写数据时,主机需要异步的将数据同步给从机,如果在同步过去之前,主机宕机了,就会出现死锁问题。

来源地址:https://blog.csdn.net/qq_47288175/article/details/127441620

--结束END--

本文标题: SETNX分布锁详解

本文链接: https://www.lsjlt.com/news/391722.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • SETNX分布锁详解
    前提 为何要使用分布式锁 其实使用分布式锁的目的与平常使用synchronized锁,Lock锁的目的一致,就是为了确保多线程并发时,在某些业务场景中让这些线程串行执行。 通常在一个JVM中让线程串行...
    99+
    2023-09-03
    jvm java 开发语言 redis 1024程序员节
  • Redis实现分布式锁(SETNX)
    目录 1、什么是分布式锁 2、分布式锁应具备的条件         3、为什么使用分布式锁 4、SETNX介绍 5、分布式锁实现 6、效果演示 7、Redisson分布式锁详解 8、Lua脚本实现可重入分布式锁 1、什么是分布式锁  ...
    99+
    2023-10-21
    redis 分布式 java spring boot 后端
  • Redisson分布式锁之加解锁详解
    目录引言锁的可重入性加锁锁续命释放锁引言 2023的金三银四来的没想象中那么激烈,一个朋友前段时间投了几十家,多数石沉大海,好不容易等来面试机会,就恰好被问道项目中关于分布式锁的应用,后涉及Redisson实现分布式锁...
    99+
    2023-03-19
    Redisson分布式加解锁 Redisson加解锁
  • Redis的setNX分布式锁超时时间失效 -1问题及解决
    目录Redis setNX分布式锁超时时间失效 -1使用SETNX加锁使用 RedisTemplate 操作Redis解决死锁Redis分布式锁,超时问题的处理分布式可能会出现的超时问题总结Redis setNX分布式锁...
    99+
    2023-01-09
    Redis分布式锁 setNX分布式锁超时时间失效 RedissetNX分布式锁超时
  • Redis的setNX分布式锁超时时间失效-1问题及解决
    目录Redis setNX分布式锁超时时间失效 -1使用SETNX加锁使用 RedisTemplate 操作Redis解决死锁Redis分布式锁,超时问题的处理分布式可能会出现的超时...
    99+
    2023-01-09
    Redis分布式锁 setNX分布式锁超时时间失效 Redis setNX分布式锁超时
  • Redis实现分布式锁详解
    目录一、前言为什么需要分布式锁?二、基于Redis实现分布式锁为什么redis可以实现分布式锁?如何实现?锁的获取锁的释放三、如何避免死锁?锁的过期时间如何设置?避免死锁锁过期处理释放其他服务的锁如何处理呢?那么redi...
    99+
    2023-04-09
    Redis实现分布式锁 Redis分布式锁
  • Redis实现分布式锁(setnx、getset、incr)以及如何处理超时情况
    目录一、通过setnx实现1、setnx key value2、get key3、getset key value小明提出了方案一:小宏说:小明的思想不严谨网上看还有一种方式(B):...
    99+
    2024-04-02
  • 详解redis分布式锁的这些坑
    目录一、白话分布式二、分布式锁何为分布式锁分布式锁的条件分布式锁的实现三、redis实现分布式锁四、redis实现分布式锁问题五、用锁遇到过哪些问题?又是如何解决的?未关闭资源B的锁...
    99+
    2024-04-02
  • redis实现分布式锁实例详解
    目录1、业务场景引入2、基础环境准备2.1.准备库存数据库2.2.创建SpringBoot工程,pom.xml中导入依赖,请注意版本。2.3.application.properti...
    99+
    2024-04-02
  • Redis如何实现分布式锁详解
    目录一、前言二、实现原理2.1 加锁2.2 解锁三、通过RedisTemplate实现分布式锁四、通过Redisson实现一、前言 在Java的并发编程中,我们通过锁,来避免由于竞争...
    99+
    2024-04-02
  • 详解基于redis实现分布式锁
    目录前言原理剖析实现编写注解拦截器拦截上述提及工具问题分析前言 为了保证一个在高并发存场景下只能被同一个线程操作,java并发处理提供ReentrantLock或Synchroniz...
    99+
    2024-04-02
  • 基于Zookeeper实现分布式锁详解
    目录1、什么是Zookeeper?2、Zookeeper节点类型3、Zookeeper环境搭建4、Zookeeper基本使用5、Zookeeper应用场景6、Zookeeper分布式...
    99+
    2024-04-02
  • 详解redis分布式锁(优化redis分布式锁的过程及Redisson使用)
    目录1. redis在实际的应用中2.如何使用redis的功能进行实现分布式锁2.1 redis分布式锁思想2.1.1设计思想:2.1.2 根据上面的设计思想进行代码实现2.2 使用...
    99+
    2024-04-02
  • DBA_2PC_PENDING中的分布式锁-解锁
     运行shell脚本后,会生成 roll.sql文件。...
    99+
    2023-06-06
  • redis分布式锁的实现原理详解
    首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 1.互斥性。在任意时刻,只有一个客户端能持有锁。 2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有...
    99+
    2024-04-02
  • 详解如何在springcloud分布式系统中实现分布式锁
    目录一、简介 二、redis命令介绍 三、实现思路 四、编码实现 五、注意点 六、参考资料 最近在看分布式锁的资料,看了 Josial L的《Redis in Action》的分布式...
    99+
    2024-04-02
  • Redis分布式锁详细介绍
    目录分布式锁redis实现分布式锁的原理死锁问题超时问题锁误放问题可重入性Redlock分布式锁 在单进程应用中,当一段代码同一时间内只能由一个线程执行时, 多线程下可能会出错,例如...
    99+
    2024-04-02
  • 【分布式】分布式锁
    目录 一、分布式锁介绍二、基于 Redis 实现分布式锁1. 如何基于 Redis 实现一个最简易的分布式锁?2.为什么要给锁设置一个过期时间?3. 如何实现锁的优雅续期?4. 如何实现可重入锁? 一、分布式锁介绍 单机多线...
    99+
    2023-08-17
    分布式 java jvm
  • Golang分布式锁详细介绍
    目录进程内加锁trylock基于redis的setnx基于zk基于etcdredlock如何选择在单机程序并发或并行修改全局变量时,需要对修改行为加锁以创造临界区。为什么需要加锁呢?...
    99+
    2024-04-02
  • SpringBoot 集成 ShedLock 分布式锁的示例详解
    目录一:ShedLock 简介二:配置maven 依赖三:SchedulerLock 基于 Redis 的配置四:在启动类中添加 @EnableScheduling 标签五:test...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作