Redission · 可重入锁(Reentrant Lock)

news2024/9/29 15:21:39

前言

Redisson是一个强大的分布式Java对象和服务库,专为简化在分布式环境中的Java开发而设计。通过Redisson,开发人员可以轻松地在分布式系统中共享数据、实现分布式锁、创建分布式对象,并处理各种分布式场景的挑战。

Redisson的设计灵感来自于Redis,但它不仅仅是Redis的Java客户端,更是在分布式环境下构建分布式系统所需的一套工具。无论是分布式集合、分布式锁、还是分布式调度器,Redisson都提供了简单而强大的API,使得开发者能够专注于业务逻辑而不必担心复杂的分布式细节。

底层原理

通过提供易于使用的API和丰富的功能集,Redisson旨在帮助开发者更轻松地构建可靠的、高性能的分布式系统。
Redisson的底层原理主要基于Redis的分布式特性和Java的高级特性。以下是一些关键的底层原理:

  1. Redis协议: Redisson使用Redis协议进行与Redis服务器的通信。这意味着它能够与任何遵循Redis协议的Redis服务器进行交互。通过利用Redis的分布式特性,Redisson实现了分布式对象和服务。
  2. Java序列化: Redisson使用Java对象的序列化和反序列化机制将Java对象转化为Redis数据结构。这使得在Java应用程序和Redis之间传递对象变得简单。默认情况下,Redisson使用标准的Java序列化,但也支持其他序列化方式,如JSON、Jackson等。
  3. 分布式锁的实现: Redisson的分布式锁是通过Redis的SETNX(set if not exists)命令实现的。它利用了Redis的原子性操作,确保在分布式环境中只有一个客户端能够成功获取锁。
  4. 监听器和事件通知: Redisson通过订阅/发布机制实现事件通知。当分布式对象发生变化时,Redisson会发布相应的事件,已注册的监听器将得到通知。这基于Redis的PUB/SUB功能,使得分布式环境下的事件通知成为可能。
  5. 分布式集群: 对于Redis集群,Redisson使用了Redis的集群模式。它能够识别集群中的不同节点,并根据需要进行数据分片和分布式操作。
  6. 线程模型: Redisson使用异步的线程模型来处理与Redis服务器的通信。这有助于提高性能,允许多个操作同时执行而不阻塞主线程。

总体而言,Redisson利用了Redis强大的分布式功能,并通过Java的特性将其封装为易于使用的API。底层的实现涵盖了分布式锁、分布式对象、事件通知等方面,以满足在分布式环境中构建高性能应用程序的需求。

Redisson分布式锁类型

Redisson提供了多种类型的分布式锁,以满足不同场景的需求。以下是一些常见的Redisson分布式锁类型:

  1. 可重入锁(Reentrant Lock): 可以被同一个线程重复加锁的锁。同一个线程在持有锁的情况下可以再次加锁,而不会引起死锁。
  2. 公平锁(Fair Lock): 公平锁按照请求加锁的顺序进行获取锁,即先来先得。这有助于避免某些线程长时间等待的问题,提高公平性。
  3. 联锁(MultiLock): 可以同时获取多个锁,且在释放锁时可以选择全部释放或部分释放。适用于需要操作多个资源的场景。
  4. 红锁(RedLock): RedLock是一种分布式锁算法,使用多个Redis节点来确保锁的强一致性。通过在不同的节点上创建锁,即使其中一个节点失效,其他节点依然可以工作。
  5. 读写锁(ReadWrite Lock): 读写锁分为读锁和写锁,多个线程可以同时持有读锁,但只有一个线程能够持有写锁。适用于读多写少的场景。
  6. 信号量(Semaphore): 类似于Java的Semaphore,用于控制同时访问某个资源的线程数量。
  7. 闭锁(CountDownLatch): 用于等待多个线程完成操作后再执行下一步操作。
  8. 过期锁(Lease Lock): 具有自动过期时间的锁,确保在一定时间内锁会被释放,避免锁长时间占用。

这些锁的类型使得Redisson适用于各种分布式场景,开发者可以根据具体的需求选择合适的锁类型来确保分布式环境下的协同和同步。

可重入锁(Reentrant Lock)

基于Redis的Redisson分布式可重入锁RLockJava对象实现了java.util.concurrent.locks.Lock接口。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

引入 Maven 依赖

在微服务的 pom.xml 引入 redisson 的 maven 依赖

        <dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.16.2</version> <!-- 使用最新版本 -->
		</dependency>

自定义配置类

下面的代码是单节点 Redis 的配置。

package com.example.demo.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class RedissonConfig {
    /**
     * 对 Redisson 的使用都是通过 RedissonClient 对象
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod="shutdown") // 服务停止后调用 shutdown 方法。
    public RedissonClient redisson() throws IOException {
        // 创建配置
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://xx.xx.x.x:6379")
                .setPassword("123456");
        // 集群模式
        // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
        return Redisson.create(config);
    }
}

测试API

package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/redis")
public class MyController {

    @Autowired
    private ReentrantLockService reentrantLockService;

    @GetMapping("/locked-operation")
    public String performLockedOperation() {
        // 模拟多个线程同时调用可重入锁的操作
        for (int i = 1; i <= 5; i++) {
            final int threadNumber = i;
            new Thread(() -> {
                reentrantLockService.performLockedOperation();
            }).start();
        }
        return "Locked operation initiated.";
    }
}

测试类

package com.example.demo.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class ReentrantLockService {

    @Autowired
    private RedissonClient redissonClient;

    public void performLockedOperation() {
        // 获取可重入锁
        RLock lock = redissonClient.getLock("myReentrantLock");

        String threadName = Thread.currentThread().getName();
        try {
            // 尝试加锁,最多等待10秒,锁的自动释放时间为30秒
            boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (isLocked) {
                // 执行需要加锁的操作
                log.info(threadName + " - 获取锁成功,执行加锁操作...");

                // 模拟业务操作
                Thread.sleep(5000);

                log.info(threadName + " - 加锁操作完成。");
            } else {
                log.info(threadName + "在指定时间内无法获取锁。");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println(threadName + "尝试获取锁时发生中断。");
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                log.info(threadName + " - 释放锁");
                lock.unlock();
            }
        }
    }
}

输出日志

2023-11-17 15:56:13.935  INFO 12316 --- [      Thread-17] c.e.d.controller.ReentrantLockService    : Thread-17 - 获取锁成功,执行加锁操作...
2023-11-17 15:56:18.945  INFO 12316 --- [      Thread-17] c.e.d.controller.ReentrantLockService    : Thread-17 - 加锁操作完成。
2023-11-17 15:56:18.969  INFO 12316 --- [      Thread-17] c.e.d.controller.ReentrantLockService    : Thread-17 - 释放锁
2023-11-17 15:56:19.020  INFO 12316 --- [      Thread-16] c.e.d.controller.ReentrantLockService    : Thread-16 - 获取锁成功,执行加锁操作...
2023-11-17 15:56:23.901  INFO 12316 --- [      Thread-19] c.e.d.controller.ReentrantLockService    : Thread-19在指定时间内无法获取锁。
2023-11-17 15:56:23.901  INFO 12316 --- [      Thread-20] c.e.d.controller.ReentrantLockService    : Thread-20在指定时间内无法获取锁。
2023-11-17 15:56:23.909  INFO 12316 --- [      Thread-18] c.e.d.controller.ReentrantLockService    : Thread-18在指定时间内无法获取锁。
2023-11-17 15:56:24.023  INFO 12316 --- [      Thread-16] c.e.d.controller.ReentrantLockService    : Thread-16 - 加锁操作完成。
2023-11-17 15:56:24.046  INFO 12316 --- [      Thread-16] c.e.d.controller.ReentrantLockService    : Thread-16 - 释放锁

分析日志

这段日志展示了一个使用Redisson实现的分布式锁的情景。让我详细解释一下日志和代码的原理:

获取锁(Thread-17):

  • Thread-17 成功获取了名为 “myReentrantLock” 的可重入锁。
  • 执行了一段需要锁保护的业务操作,模拟了一个长时间的操作,持有锁。

释放锁(Thread-17):

- `Thread-17` 完成了业务操作,释放了锁。

获取锁(Thread-16):

  • Thread-16Thread-17 释放锁之后成功获取了相同的锁。
  • 执行了一段需要锁保护的业务操作,然后释放了锁。

获取锁失败(Thread-19, Thread-20, Thread-18):

  • Thread-19, Thread-20, 和 Thread-18 在指定的等待时间内无法获取锁,因为此时 Thread-16 持有锁。

总结原理:

  • 通过redissonClient.getLock("myReentrantLock")创建了一个可重入锁对象。
  • lock.tryLock(10, 30, TimeUnit.SECONDS)尝试获取锁,在10秒内等待,锁的自动释放时间为30秒。
  • 如果获取锁成功,执行需要加锁的操作,然后释放锁。
  • 其他线程在获取锁时,如果超过指定时间未能成功获取,会得到相应的提示。

这段代码通过Redisson实现了一个可重入的分布式锁,确保在分布式环境下对共享资源的安全访问。成功获取锁的线程执行受保护的操作,其他线程则需要等待或处理获取锁失败的情况。这有助于协调分布式系统中的并发访问,防止竞争条件和数据不一致性。

阻塞与非阻塞

阻塞方式

  • 在阻塞方式中,线程在尝试获取锁时,如果锁已被其他线程占用,那么当前线程会被阻塞,一直等到锁被释放后才能继续执行。在阻塞模式下,线程可能会等待相当长的时间,直到获取到锁。
ReentrantLock lock = new ReentrantLock();

// 阻塞方式获取锁
lock.lock();
try {
    // 执行需要锁保护的代码
} finally {
    lock.unlock();
}

非阻塞方式

  • 在非阻塞方式中,线程尝试获取锁时,如果锁已被其他线程占用,当前线程不会被阻塞,而是立即返回一个结果,告知是否成功获取锁。非阻塞方式下,线程不会等待,而是可以继续执行其他操作。
ReentrantLock lock = new ReentrantLock();

// 非阻塞方式尝试获取锁
if (lock.tryLock()) {
    try {
        // 执行需要锁保护的代码
    } finally {
        lock.unlock();
    }
} else {
    // 未获取到锁的处理逻辑
}

看门狗Watchdog

请在此添加图片描述

Redisson 使用看门狗(Watchdog)机制来保持分布式锁的有效性。看门狗是一种定时任务,负责定期延长锁的过期时间,确保在业务执行时间较长或者发生异常情况时,锁不会过早释放。

下面是 Redisson 看门狗的简要原理:

  1. 锁的过期时间: 当获取分布式锁时,会设置锁的过期时间(通常是锁的租期)。这个过期时间是在 Redis 中设置的,表示锁在这段时间内有效。
  2. 看门狗的作用: Redisson 的看门狗定期(比如每隔一定时间)检查当前线程持有的锁是否过期。如果锁的过期时间快到了,看门狗会尝试续租,延长锁的过期时间。
  3. 续租操作: 续租操作是通过发送一个延长锁过期时间的命令到 Redis。如果当前线程在续租时发生了异常,比如网络异常,看门狗会尽力保证在后续的定时任务中继续尝试续租。
  4. 锁的释放: 如果看门狗发现锁已经过期且无法续租,它会尝试删除锁,释放资源。这是为了防止因为业务执行时间较长或者发生异常情况导致锁一直被占用而不释放。
  5. 线程关闭时的处理: Redisson 看门狗还处理了线程关闭的情况。如果获取锁的线程关闭了,看门狗会立即释放锁,以避免死锁情况。

通过看门狗机制,Redisson 能够确保在使用分布式锁的场景下,锁不会因为持有锁的线程异常退出或者执行时间过长而导致锁被过早释放。这提高了分布式锁的可靠性和稳定性。

源码解析

请在此添加图片描述

这段代码是 Redisson 中续租锁过期时间的方法。让我们逐步解析其中的关键部分:

renewExpiration 方法: 这个方法用于执行锁的过期时间续租操作。

private void renewExpiration() {
    ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    if (ee == null) {
        return;
    }

    // 创建定时任务,定时执行续租操作
    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            // 获取续租信息
            ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
            if (ent == null) {
                return;
            }
            Long threadId = ent.getFirstThreadId();
            if (threadId == null) {
                return;
            }

            // 异步执行续租操作
            RFuture<Boolean> future = renewExpirationAsync(threadId);
            future.onComplete((res, e) -> {
                if (e != null) {
                    // 续租失败,记录错误日志,移除续租信息
                    log.error("Can't update lock " + getRawName() + " expiration", e);
                    EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                    return;
                }

                if (res) {
                    // 续租成功,重新调度续租任务
                    renewExpiration();
                } else {
                    // 续租失败,取消续租任务
                    cancelExpirationRenewal(null);
                }
            });
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 续租时间为租约时间的1/3

    // 将定时任务绑定到续租信息中
    ee.setTimeout(task);
}

internalLockLeaseTime 变量

请在此添加图片描述

定时任务创建: 使用 commandExecutor.getConnectionManager().newTimeout 创建一个定时任务,这个任务会在 internalLockLeaseTime / 3 毫秒后执行。

续租操作: 在定时任务执行时,异步执行 renewExpirationAsync 方法,该方法负责向 Redis 发送命令更新锁的过期时间。

回调处理: 在异步续租操作完成时,根据续租操作的结果,进行相应的处理。

  • 如果续租成功,重新调度下一次续租任务。
  • 如果续租失败,取消续租任务,并记录错误日志。

这个机制通过定时任务实现了定期的锁续租,确保分布式锁在持有期间不会因为过期而被自动释放。

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

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

相关文章

华为OD机试 - 静态扫描(Java 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

八LAMP搭建

# LAMP ## 一、知识回顾 ### FTP samba nfs 特点 - 借用Linux用户作为映射用户&#xff0c;进行权限管理 - 软件本身还有管理控制权限 #### 客户端连接到服务器后进行读写执行等操作 &#xff0c;必须同时具有: - 软件许可的权限 vsftpd: anon upload enableYES - 文件…

PC端微信小程序如何调试?

向往常一样运行开微信小程序开发者工具 如果只弹出pc端小程序&#xff0c;没有出现调试的界面&#xff1a;点击胶囊按钮的三个…选择重新进入小程序 即可依次展开相应的功能调试&#xff0c;改完代码没反应再刷新看看&#xff0c;再没反应就再次重新点击编译并自动调试。

fish-speech语音大模型本地部署

文章目录 fish-speech模型下载编译部署 小结 fish-speech模型 先说下fish-speech模型吧&#xff0c;可以先看下官网。如下&#xff1a; 这就是一个模型&#xff0c;可以根据一个样例声音&#xff0c;构建出自己需要的声音。其实&#xff0c;这个还是有很多用途的&#xff1b;…

Tpflow:提升开发效率的PHP工作流引擎

Tpflow&#xff1a;提升开发效率的PHP工作流引擎 今天要跟大家介绍的是一款能够显著提升开发效率的工具——Tpflow&#xff01;它是一个专门为PHP开发者设计的工作流引擎&#xff0c;致力于帮助你减少80%以上的代码量&#xff0c;轻松管理复杂的业务流程。让我们一起来看看它的…

win10安装Java闪退

问题&#xff1a;win10 在安装Java8时&#xff0c;电脑没有反应&#xff0c;出现闪退的现象。 1、打开设置找到“时间和语言” 2、找到“中文&#xff08;简体&#xff0c;中国&#xff09;” 3、点击“选项” 4、删除“百度输入法” 5、问题解决&#xff0c;再次安装Java

史上最细快速排序讲解(hoare,挖坑,双指针, 非递归)

文章目录 前言一、递归方法快排1. 递归主要思想2. 递归代码实现 二、hoare方法实现找基准值1. hoare思想2. hoare代码实现 三、挖坑方法实现找基准值1. 挖坑思想2. 挖坑代码实现 四、双指针方法实现找基准值1. lomuto前后指针法思想2. lomuto前后指针法代码实现 五、非递归方法…

第十四周:机器学习笔记

第十四周周报 摘要Abstract一、机器学习——Transformer&#xff08;下&#xff09;1. Transformer decoder1.1 autoregressive decoder&#xff08;自回归解码器&#xff09;1.2 Transformer decoder结构图1.3 non-autoregressive decoder&#xff08;非自回归解码器&#xff…

24年最新 idea 插件开发教程,面试鸭插件技术实现!

大家好&#xff0c;我是松柏。今天给大家分享下这个JetBrains插件开发教程。 学习过程中可以参考开源项目&#xff1a;https://github.com/yuyuanweb/mianshiya-plugin 首先贴一下官方文档&#xff1a;https://plugins.jetbrains.com/docs/intellij/welcome.html 虽然这个文档…

【最新发布】Win10 22H2 19045.4957 正式版免费下载!

今日系统之家小编给大家分享2024年9月第二次更新的Windows10 22H2正式版系统&#xff0c;该版本系统基于最新Windows10 22H2 19045.4957 64位专业版进行离线制作&#xff0c;安全无毒&#xff0c;修复了使用某些环绕声技术时某些媒体的播放可能会停止等多项问题&#xff0c;系统…

基于C#开发的(CS界面)图书管理系统

图书管理系统项目开发说明书 项目简介 项目背景&#xff08;选择这个项目的原因、前景&#xff0c;面向的用户&#xff0c;优势&#xff09;&#xff1b; 根据温州理工学院需要希望能够充分利用现代科技来提高图书管理的效率&#xff0c;在原有的办公系统基础上进行扩展&…

1. 如何在服务器上租GPU跑实验 (以AutoDL为例) - 深度学习·科研实践·从0到1

目录 前言 1. 在AutoDL上注册账号 2. 在算力市场选择GPU 3. 创建实例 4. 控制台-容器实例界面&#xff08;核心&#xff09; 4.1 无卡模式&#xff08;常用&#xff09; 5. 帮助文档 前言 好记性不如烂笔头&#xff0c;本专栏将详细记录下本人学习深度学习工程实践&…

Python通过Sqlalchemy框架实现增删改查

目录 简介 什么是SQLAlchemy&#xff1f; SQLAlchemy可以分为两个部分&#xff1a;Core和ORM。 一、首先安装sqlalchemy 二、在配置文件中添加数据库连接信息&#xff0c;我这里是Mysql 三、 创建数据库连接类&#xff0c;我这里是动态读取数据库的表字段&#xff0c;自动…

神器!GPT让大学生也能轻松实现架构师级的系统设计图

文章目录 零、前言一、实现架构师级的系统设计图操作指导系统背景功能细化 画用例图画系统架构设计图划分html页面画实体类图画服务层类图画时序图画数据库ER图 二、感受 零、前言 粉丝做毕业设计时&#xff0c;不会画架构图&#xff0c;问虚竹哥会不会画&#xff5e; 虽然这…

基于微信小程序的空巢老人健康管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

STM32引脚PB3、PB4、PA15作为输入输出的特殊配置

一、问题描述 简单描述&#xff1a; 最近做的一个项目中&#xff0c;PB3端口配置为输入&#xff0c;不管外部输入是高电平还是低电平&#xff0c;一直读取到的是低电平。 调试过程&#xff1a;在撰写代码过程中&#xff0c;又发现新的问题&#xff0c;Enter按键无法控制屏幕数…

电脑显示缺失msvcp140_1.dll怎样修复,5种快速修复方法让你快速修复

1. msvcp140_1.dll 定义 1.1 Microsoft Visual C 2015 Redistributable组件 msvcp140_1.dll 是 Microsoft Visual C 2015 Redistributable 的关键组件之一&#xff0c;扮演着至关重要的角色。以下是对 msvcp140_1.dll 的详细分析&#xff1a; 组件功能&#xff1a;msvcp140_…

《中国电子报》报道: 安宝特AR为产线作业者的“秘密武器

近日&#xff0c;中国电子报在其文章《下一代工业智能终端重新定义制造业》中对安宝特的增强现实&#xff08;AR&#xff09;解决方案给予了高度评价&#xff0c;称其为产线作业者的“秘密武器”。这一创新技术改变了传统制造业的作业方式&#xff0c;使得操作人员能够在生产过…

Ubuntu中交叉编译armadillo库

网上关于交叉编译armadillo库比较少&#xff0c;借鉴了一些但是在前几天编译时总是磕磕绊绊&#xff0c;于是写一个详细的编译过程。 交叉编译armadillo库包含两个步骤&#xff1a;交叉编译依赖库和交叉编译armadillo。armadillo官网介绍依赖库如下图所示&#xff1a; 需要注意…