前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java实现Redis分布式锁

Java实现Redis分布式锁

原创
作者头像
BLACK595
发布2024-09-23 09:25:38
1120
发布2024-09-23 09:25:38

前言

为了让同一时刻资源只能一个线程访问,也就是互斥访问共享资源,在单机环境中,我们通常会使用JVM本地锁、volatile、concurrent并发包等方式实现。但随着系统规模的扩大,系统升级成了分布式系统,一个服务会部署到不同服务器上,不同机器不同进程,就需要在多进程下保证线程的安全性了。因此,分布式锁应运而生。

原理

分布式锁的基本原理:

  1. 请求锁:当一个线程实例需要访问共享资源时,他会向分布式锁系统发送一个请求,试图获取锁。
  2. 锁定资源:分布式锁系统判断当前锁是否被其他实例占用,如果没有被占用,则当前实例获取成功,锁定资源;如果已经被占用,当前实例获取失败,继续等待或直接放弃。
  3. 访问资源:当前实例获取成功后,则可以放心安全使用资源,直到业务执行完毕。
  4. 释放资源:使用完毕后,通知分布式锁系统释放资源,放给其他实例使用。
image.png
image.png

Redis实现

  • SETNX key value SETNX的全称是SET IF NOT EXIST(如果不存在则设置key),当设置成功后返回1,设置失败后返回0。
  • EXPIRE key seconds 设置key的过期时间
  • DEL key 删除key

所以使用redis加锁的流程为:

代码语言:java
复制
// 加锁 
SETNX lock_key 1 
// 1分钟后过期
EXPIRE lock_key 60
// 业务逻辑 
DO THINGS 
// 释放锁 
DEL lock_key

但这个操作并不是原子性的,如果加锁过后,Redis宕机,锁就一直释放不了,就会造成死锁。所以最好把这段命令使用Lua脚本,或者使用Redis给我们提供的SETNX和EXPIRE合并的命令SET key value EX seconds NXSET lock_key 1 EX 60 NX

不足:

  • 不可重入:Redis分布式锁默认是不可重入的,如果需要可重入,需要额外的逻辑来实现。
  • 非阻塞:Redis分布式锁是非阻塞的,如果获取锁失败,需要自己进行重试。

Redission实现

Redis虽然可以完成分布式锁的实现,但是还有有不可重入、非阻塞等缺点,使用Redission就给我们大大降低了使用成本,Redission使用大量的Lua脚本和Netty,解决了Redis锁不可重入、非阻塞等缺点。

引入Redission

代码语言:java
复制
<!-- redisson 分布式锁 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.27.2</version>
</dependency>

使用Redission

Redisson支持单点模式、主从模式、哨兵模式、集群模式,这里以单点模式为例,模拟5个线程同时抢1个红包:

代码语言:java
复制
public void test() throws InterruptedException {
    String key = "red_paper_id";
    long waitTimeout = 100;  //100毫秒
    long leaseTime = 2000; //2000毫秒
    for (int i = 1; i <= 5; i++) {
        new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            RLock lock = redissonClient.getLock(key);
            try {
                /* waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
                 * leaseTime   锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)*/
                boolean res = lock.tryLock(waitTimeout, leaseTime, TimeUnit.MILLISECONDS);
                if(res){
                    System.out.println(threadName + "获取到锁");
                    Thread.sleep(1000); //模拟业务处理
                    boolean res2 = lock.tryLock(waitTimeout, leaseTime, TimeUnit.MILLISECONDS);
                    if(res2){
                        System.out.println(threadName + "重新获取锁成功");
                    } else {
                        System.out.println(threadName + "重新获取锁失败");
                    }
                    //释放锁
                    lock.unlock();
                    System.out.println(threadName + "释放锁");
                } else {
                    System.out.println(threadName + "没有获取到锁");
                }
            } catch (Exception e) {
                System.out.println(threadName + "获取锁失败");
            }
        }, "线程" + i).start();
    }
    //等待所有线程执行完
    Thread.sleep(3000);
}

执行结果:

代码语言:txt
复制
线程1获取到锁
线程2没有获取到锁
线程4没有获取到锁
线程3没有获取到锁
线程5没有获取到锁
线程1重新获取锁成功
线程1释放锁

可以看到Redission将原子性的操作都封装起来了,我们只需要调用tryLock尝试获取锁并设置过期时间,并且再次tryLock时能够重入获取到锁;其他线程实例也能在给定时间内重试获取锁。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 原理
  • Redis实现
  • Redission实现
    • 引入Redission
      • 使用Redission
      相关产品与服务
      云数据库 Redis®
      腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档