当前位置: 首页 > news >正文

架构设计之Redisson分布式锁-可重入同步锁(一)

架构设计之Redisson分布式锁-可重入同步锁(一)

Redisson分布式锁官方博客地址

1、Redisson是什么

Redisson 是一个基于 RedisJava 分布式工具库,它提供了 分布式锁、集合、队列、缓存、Map、限流、任务调度 等高级数据结构和功能,极大地简化了 Java 应用在分布式环境中的开发。

2、一个靠谱分布式锁需要具备的条件和刚需

1、独占性

OnlyOne,任何时刻只能有且仅有一个线程持有

2、高可用

若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况

3、防死锁

杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案

4、不乱抢

防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放。

5、重入性

同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁。

3、Redisson 锁相关的API

// 获取一个可重入的分布式锁(普通锁)
RLock getLock(String var1);// 获取一个自旋锁,适用于短时间内高频率获取锁的场景
RLock getSpinLock(String var1);// 获取带 **回退策略**(BackOff)的自旋锁,可以减少 CPU 资源占用
RLock getSpinLock(String var1, LockOptions.BackOff var2);// 获取一个围栏锁(Fenced Lock),用于与 **基于数据库的事务** 结合使用,确保数据一致性
RFencedLock getFencedLock(String var1);// 获取多个锁的组合锁(联锁),所有锁 **必须** 都获取成功才算加锁成功
RLock getMultiLock(RLock... var1);// **已废弃**,RedLock 是基于 Redis 作为存储的分布式锁算法,但因 **不适用于所有场景**,官方已不推荐使用
/** @deprecated */
@Deprecated
RLock getRedLock(RLock... var1);// 获取一个 **公平锁**,保证获取锁的顺序与请求锁的顺序相同(FIFO)
RLock getFairLock(String var1);// 获取一个 **读写锁**,支持多个读锁同时存在,写锁是独占的
RReadWriteLock getReadWriteLock(String var1);

4、新建 xx-redisson

1、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"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.0</version><relativePath/></parent><artifactId>xx-redisson</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--redisson--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.25.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.1.0</version></dependency></dependencies></project>

2、application.yml

server:port: 8001servlet:context-path: /redissonspring:data:redis:database: 0host: 127.0.0.1password: 123456port: 6379

3、启动类

package com.xx;import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.util.StopWatch;import java.net.InetAddress;
import java.net.UnknownHostException;/*** @Author: xueqimiao* @Date: 2025/3/20 13:33*/
@SpringBootApplication
@Slf4j
public class RedissonApplication {public static void main(String[] args) throws UnknownHostException {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext application = SpringApplication.run(RedissonApplication.class, args);stopWatch.stop();Environment env = application.getEnvironment();String ip = InetAddress.getLocalHost().getHostAddress();String port = env.getProperty("server.port");String path = env.getProperty("server.servlet.context-path");log.info("\n--------------------------------------------------------\n\t" +"Application Manager is running! Access URLs:\n\t" +"Local: \t\thttp://127.0.0.1:" + port + path + "/\n\t" +"External: \thttp://" + ip + ":" + port + path + "/\n\t" +"Swagger文档: \thttp://" + ip + ":" + port + path + "/doc.html\n\t" +"服务启动完成,耗时: \t" + stopWatch.getTotalTimeSeconds() + "S\n" +"----------------------------------------------------------");}
}

4、Result 统一返回

package com.xx.common;import lombok.Data;
import org.springframework.http.HttpStatus;/*** @Author: xueqimiao* @Date: 2024/4/28 10:33*/
@Data
public class Result<T> {private String code;private String message;private T data;private long timestamp ;public Result (){this.timestamp = System.currentTimeMillis();}public static <T> Result<T> ok() {Result<T> result = new Result<>();result.setCode(String.valueOf(HttpStatus.OK.value()));result.setMessage("操作成功");return result;}public static <T> Result<T> ok(T data) {Result<T> result = new Result<>();result.setCode(String.valueOf(HttpStatus.OK.value()));result.setMessage("操作成功");result.setData(data);return result;}public static <T> Result<T> error(String message) {Result<T> result = new Result<>();result.setCode(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));result.setMessage(message);return result;}public static <T> Result<T> error(String code, String message) {Result<T> result = new Result<>();result.setCode(code);result.setMessage(message);return result;}}

5、RedisConfig 配置类

package com.xx.config;import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @Author: xueqimiao* @Date: 2025/3/20 13:59*/
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.data.redis")
public class RedisConfig {/*** 主机*/private String host;/*** 端口*/private Integer port;/*** 密码*/private String password;/*** 数据库*/private Integer database;@Beanpublic RedissonClient getRedissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://" + host + ":" + port + "").setDatabase(database).setPassword(password);return Redisson.create(config);}}

6、Controller

package com.xx.controller;import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.redisson.api.RedissonClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Author: xueqimiao* @Date: 2025/3/20 13:34*/
@RequestMapping("/redisson")
@RestController
@Tag(name = "Redison 分布式锁 相关API")
public class RedissonController {@Resourceprivate RedissonClient redissonClient;}

5、可重入的分布式锁getLock

1、lock() 无参数

该方法 不会自动释放锁,如果应用崩溃或异常,可能导致锁长时间无法释放。

private int stock = 3; // 模拟库存/*** 方式 1:使用 lock() 方法(默认加锁,不会自动释放)*/
@SneakyThrows
@GetMapping("/lock")
@Operation(summary = "lock() 无参数")
public Result lock() {// 获取锁RLock lock = redissonClient.getLock("stockLock");lock.lock(); // 加锁(需手动释放)try {// 模拟库存扣减if (stock > 0) {stock--;
//                TimeUnit.SECONDS.sleep(10);System.out.println("库存扣减成功,剩余库存:" + stock);} else {System.out.println("库存不足!");}return Result.ok("操作成功,剩余库存:" + stock);} finally {lock.unlock(); // 释放锁}
}

2、lock() 带超时时间

该方法 10 秒后自动释放锁,适用于 避免死锁 的场景。

/*** 方式 2:使用 lock(long leaseTime, TimeUnit unit) 方法(加锁一段时间后自动释放)*/
@GetMapping("/lockWithTimeout")
@Operation(summary = "lock(leaseTime, unit) 带超时时间")
public Result lockWithTimeout() {// 获取锁RLock lock = redissonClient.getLock("stockLock");// 加锁 10 秒,10 秒后自动释放lock.lock(10, TimeUnit.SECONDS);try {// 模拟库存扣减if (stock > 0) {stock--;System.out.println("库存扣减成功,剩余库存:" + stock);} else {System.out.println("库存不足!");}return Result.ok("操作成功,剩余库存:" + stock);} finally {lock.unlock(); // 释放锁}
}

3、lock() 小结

方法适用场景
lock()确保拿到锁后才会执行任务,适用于 必须串行执行的任务,但可能导致死锁。
lock(leaseTime, unit)适用于防止死锁,即使崩溃也会在 leaseTime 后自动释放锁。

4、tryLock() 无参数

立即尝试获取锁,获取不到直接返回失败

  /*** 方式 1:tryLock() 无参数(立即尝试获取锁,获取不到直接返回失败)*/@SneakyThrows@GetMapping("/tryLock")@Operation(summary = "tryLock() 无参数")public Result tryLock() {// 获取锁RLock lock = redissonClient.getLock("stockLock");// 立即尝试加锁,如果获取不到,直接返回if (!lock.tryLock()) {System.out.println("获取锁失败,请稍后再试");return Result.error("获取锁失败,请稍后再试");}try {// 模拟库存扣减if (stock > 0) {stock--;
//                TimeUnit.SECONDS.sleep(10);System.out.println("库存扣减成功,剩余库存:" + stock);} else {System.out.println("库存不足!");}return Result.ok("操作成功,剩余库存:" + stock);} finally {lock.unlock(); // 释放锁}}

5、tryLock(waitTime, unit) 带最大等待时间 推荐

/*** 方式 2:tryLock(long waitTime, TimeUnit unit)* - 尝试获取锁,如果在 waitTime 时间内获取不到,则放弃*/
@SneakyThrows
@GetMapping("/tryLockWithWaitTime")
@Operation(summary = "tryLock(waitTime, unit) 带最大等待时间")
public Result tryLockWithWaitTime() {// 获取锁RLock lock = redissonClient.getLock("stockLock");try {// 最多等待 3 秒获取锁,如果超时还未获取到,则返回失败if (lock.tryLock(3, TimeUnit.SECONDS)) {try {// 模拟库存扣减if (stock > 0) {stock--;TimeUnit.SECONDS.sleep(10);System.out.println("库存扣减成功,剩余库存:" + stock);} else {System.out.println("库存不足!");}return Result.ok("操作成功,剩余库存:" + stock);} finally {lock.unlock(); // 释放锁}} else {return Result.error("获取锁失败,请稍后再试");}} catch (InterruptedException e) {Thread.currentThread().interrupt();return Result.error("线程中断异常");}
}

6、tryLock(waitTime, leaseTime, unit) 带超时时间 推荐

/*** 方式 3:tryLock(long waitTime, long leaseTime, TimeUnit unit)* - 尝试获取锁,如果在 waitTime 时间内获取不到,则放弃* - 获取到锁后,leaseTime 时间后自动释放锁(防止死锁)*/
@SneakyThrows
@GetMapping("/tryLockWithTimeout")
@Operation(summary = "tryLock(waitTime, leaseTime, unit) 带超时时间")
public Result tryLockWithTimeout() {// 获取锁RLock lock = redissonClient.getLock("stockLock");try {// 最多等待 3 秒获取锁,获取后 10 秒自动释放锁if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {try {// 模拟库存扣减if (stock > 0) {stock--;// TimeUnit.SECONDS.sleep(10);System.out.println("库存扣减成功,剩余库存:" + stock);} else {System.out.println("库存不足!");}return Result.ok("操作成功,剩余库存:" + stock);} finally {lock.unlock(); // 释放锁}} else {return Result.error("获取锁失败,请稍后再试");}} catch (InterruptedException e) {Thread.currentThread().interrupt();return Result.error("线程中断异常");}
}

7、tryLock() 小结

方法作用适用场景
tryLock()立即尝试获取锁,获取不到直接返回高并发抢占资源,不会阻塞线程
tryLock(waitTime, unit)最多等待一段时间 获取锁,获取不到则返回短时间等待获取资源,避免长时间阻塞
tryLock(waitTime, leaseTime, unit)等待一定时间获取锁,获取后 自动释放防止死锁,确保锁不会永久占用

8、释放锁的正确写法

if (lock.isHeldByCurrentThread()) { // 确保当前线程持有锁lock.unlock(); // 释放锁
}
// 避免出现以下错误
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 3dbb3527-6e66-4cb9-8837-810a6ba39da0 thread-id: 540at org.redisson.RedissonBaseLock.lambda$unlockAsync0$2(RedissonBaseLock.java:292) ~[redisson-3.25.0.jar:3.25.0]at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[na:na]at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911) ~[na:na]at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[na:na]at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147) ~[na:na]

9、isHeldByCurrentThread()

/*** 检查当前线程是否持有锁*/
@GetMapping("/isHeldByCurrentThread")
@Operation(summary = "检查当前线程是否持有锁")
public Result isHeldByCurrentThread() {RLock lock = redissonClient.getLock("testLock");boolean beforeLock = lock.isHeldByCurrentThread(); // 检查加锁前System.out.println("加锁前,当前线程是否持有锁:" + beforeLock);lock.lock(); // 加锁boolean afterLock = lock.isHeldByCurrentThread(); // 检查加锁后System.out.println("加锁后,当前线程是否持有锁:" + afterLock);lock.unlock(); // 释放锁return Result.ok("加锁前:" + beforeLock + ",加锁后:" + afterLock);
}

判断是否需要释放锁,防止 IllegalMonitorStateException

10、getHoldCount()

/*** 获取当前线程持有的锁次数*/
@GetMapping("/getHoldCount")
@Operation(summary = "获取当前线程持有锁的次数")
public Result getHoldCount() {RLock lock = redissonClient.getLock("testLock");try {lock.lock(); // 第一次加锁lock.lock(); // 第二次加锁(可重入)int holdCount = lock.getHoldCount(); // 获取当前线程持有锁的次数System.out.println("当前线程持有锁的次数:" + holdCount);return Result.ok("当前线程持有锁的次数:" + holdCount);} finally {// 确保锁的完全释放while (lock.getHoldCount() > 0) {System.out.println("解锁");lock.unlock();}}
}

避免锁未完全释放,确保 unlock() 次数匹配

11、多次加锁场景举例

1、递归调用加锁
/*** 1️⃣ 递归加锁*/
@GetMapping("/recursiveLock")
@Operation(summary = "递归调用加锁")
public Result recursiveLock() {RLock lock = redissonClient.getLock("recursiveLock");recursiveMethod(lock, 3); // 递归调用 3 次return Result.ok("递归调用完成");
}private void recursiveMethod(RLock lock, int count) {if (count == 0) {return;}lock.lock(); // 每次递归都加锁try {System.out.println("递归层级:" + count + ",当前锁持有次数:" + lock.getHoldCount());recursiveMethod(lock, count - 1);} finally {System.out.println("解锁");lock.unlock(); // 确保每次释放}
}
2、父子方法加锁
/*** 2️⃣ 父子方法加锁*/
@GetMapping("/parentChildLock")
@Operation(summary = "父子方法加锁")
public Result parentChildLock() {RLock lock = redissonClient.getLock("parentChildLock");parentMethod(lock);return Result.ok("父子方法加锁测试完成");
}private void parentMethod(RLock lock) {lock.lock();try {System.out.println("父方法获取锁,当前锁持有次数:" + lock.getHoldCount());childMethod(lock);} finally {lock.unlock();System.out.println("父方法释放锁,当前锁持有次数:" + lock.getHoldCount());}
}private void childMethod(RLock lock) {lock.lock(); // 子方法再次加锁try {System.out.println("子方法获取锁,当前锁持有次数:" + lock.getHoldCount());} finally {lock.unlock();System.out.println("子方法释放锁,当前锁持有次数:" + lock.getHoldCount());}
}
3、多层业务逻辑加锁
/*** 3️⃣ 多层业务逻辑加锁*/
@GetMapping("/multiServiceLock")
@Operation(summary = "多层业务加锁")
public Result multiServiceLock() {RLock lock = redissonClient.getLock("multiServiceLock");firstService(lock);return Result.ok("多层业务加锁测试完成");
}private void firstService(RLock lock) {lock.lock();try {System.out.println("第一层业务逻辑执行,锁次数:" + lock.getHoldCount());secondService(lock);} finally {System.out.println("解锁");lock.unlock();}
}private void secondService(RLock lock) {lock.lock();try {System.out.println("第二层业务逻辑执行,锁次数:" + lock.getHoldCount());} finally {System.out.println("解锁");lock.unlock();}
}
4、确保锁完整释放
/*** 4️⃣ 确保锁完整释放*/
@GetMapping("/ensureUnlock")
@Operation(summary = "确保锁完全释放")
public Result ensureUnlock() {RLock lock = redissonClient.getLock("ensureUnlock");try {lock.lock();lock.lock(); // 加两次锁System.out.println("当前锁持有次数:" + lock.getHoldCount());// 释放所有持有的锁while (lock.getHoldCount() > 0) {lock.unlock();System.out.println("释放一次锁,剩余持有次数:" + lock.getHoldCount());}return Result.ok("锁已完全释放");} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}
}

12、isHeldByThread()

检查指定线程是否持有锁

@SneakyThrows
@GetMapping("/isHeldByThread")
@Operation(summary = "检查指定线程是否持有锁")
public Result isHeldByThread() {RLock lock = redissonClient.getLock("testLock");// 启动新线程进行加锁Thread thread = new Thread(() -> {lock.lock();try {System.out.println("子线程持有锁:" + lock.isHeldByCurrentThread()); // trueTimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}});thread.start();// 等待子线程获取锁TimeUnit.SECONDS.sleep(1);// 获取子线程 IDlong threadId = thread.getId();// 判断子线程是否持有锁boolean isHeld = lock.isHeldByThread(threadId);System.out.println("主线程检查子线程是否持有锁:" + isHeld);return Result.ok("子线程是否持有锁:" + isHeld);
}

13、指定线程是否持有该锁场景

1、主线程等待子线程释放锁

主线程 需要等待 子线程 完成任务并释放锁

如果子线程 还持有锁,主线程就 等待一段时间,防止强制执行错误操作。

@SneakyThrows
@GetMapping("/waitForThreadLock")
@Operation(summary = "主线程等待子线程释放锁")
public Result waitForThreadLock() {RLock lock = redissonClient.getLock("threadLock");// 创建子线程并加锁Thread worker = new Thread(() -> {lock.lock();try {System.out.println("子线程持有锁:" + lock.isHeldByCurrentThread());TimeUnit.SECONDS.sleep(5); // 模拟 5 秒业务执行} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();System.out.println("子线程释放锁");}});worker.start();// 等待子线程获取锁TimeUnit.SECONDS.sleep(1);long threadId = worker.getId();while (lock.isHeldByThread(threadId)) {System.out.println("主线程检测到子线程仍然持有锁,等待释放...");try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}System.out.println("子线程已释放锁,主线程继续执行...");return Result.ok("主线程检测到锁释放,继续执行");
}
2、防止错误释放其他线程的锁

只允许 当前线程释放自己加的锁,防止误解锁 其他线程的锁

@SneakyThrows
@GetMapping("/preventUnlockError")
@Operation(summary = "防止错误释放其他线程的锁")
public Result preventUnlockError() {RLock lock = redissonClient.getLock("preventUnlock");Thread worker = new Thread(() -> {lock.lock();try {System.out.println("子线程持有锁:" + lock.isHeldByCurrentThread());TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();System.out.println("子线程释放锁");}});worker.start();// 等待子线程获取锁TimeUnit.SECONDS.sleep(1);long threadId = worker.getId();if (lock.isHeldByThread(threadId)) {System.out.println("主线程检测到子线程持有锁,不能强制解锁!");return Result.error("不能解锁其他线程持有的锁!");} else {return Result.ok("锁已释放,主线程可以继续执行");}
}
3、定期检查某个任务是否被锁定

任务调度分布式任务管理 中,判断 某个任务是否正在被另一个线程执行

如果 任务已经被锁定,可以 跳过执行,防止多个线程重复处理同一个任务。

@SneakyThrows
@GetMapping("/checkTaskLock")
@Operation(summary = "检查任务是否被锁定")
public Result checkTaskLock() {RLock lock = redissonClient.getLock("taskLock");// 模拟任务线程Thread taskThread = new Thread(() -> {lock.lock();try {System.out.println("任务线程执行中...");TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();System.out.println("任务线程执行完成");}});taskThread.start();// 等待任务线程获取锁TimeUnit.SECONDS.sleep(1);long threadId = taskThread.getId();if (lock.isHeldByThread(threadId)) {return Result.error("任务正在执行,跳过此次调度!");} else {return Result.ok("任务可执行");}
}
4、检测死锁

分布式系统 中,可能出现 某个线程获取锁后崩溃,导致 锁未释放

可以用 isHeldByThread(threadId) 检测 某个线程是否一直持有锁,如果超过一定时间仍未释放,则可以 手动解锁

@SneakyThrows
@GetMapping("/detectDeadlock")
@Operation(summary = "检测死锁并手动解锁")
public Result detectDeadlock() {RLock lock = redissonClient.getLock("deadlockLock");Thread deadThread = new Thread(() -> {lock.lock();try {System.out.println("线程加锁,但意外崩溃...");TimeUnit.SECONDS.sleep(9999); // 模拟死锁} catch (InterruptedException e) {Thread.currentThread().interrupt();}});deadThread.start();// 等待 2 秒,确保子线程成功加锁TimeUnit.SECONDS.sleep(2);long threadId = deadThread.getId();long startTime = System.currentTimeMillis();long deadlockThreshold = 10_000; // 10 秒// 等待一段时间,判断是否长时间持有锁while (lock.isHeldByThread(threadId)) {long elapsedTime = System.currentTimeMillis() - startTime;if (elapsedTime > deadlockThreshold) {System.out.println("检测到死锁,超过 10 秒,尝试解锁...");lock.forceUnlock(); // 强制解锁return Result.ok("死锁检测到并已解锁!");}TimeUnit.SECONDS.sleep(1); // 每秒检查一次}return Result.ok("没有死锁");
}

14、forceUnlock()

forceUnlock() 是 Redisson 分布式锁 提供的 强制解锁方法,它 不管当前锁是否被其他线程持有,都会直接释放锁

  • 即使锁被其他线程持有,也会 立即解除
  • 不检查线程 ID,不像 unlock() 需要 持有锁的线程才能释放

15、lock.lockInterruptibly()

1、普通可中断加锁
@GetMapping("/lockInterruptibly")
@Operation(summary = "演示可中断加锁")
public Result lockInterruptibly() throws InterruptedException {RLock lock = redissonClient.getLock("interruptibleLock");Thread thread = new Thread(() -> {try {System.out.println("线程尝试获取锁...");lock.lockInterruptibly(); // 可中断加锁System.out.println("线程成功获取锁,执行任务...");TimeUnit.SECONDS.sleep(5); // 模拟业务执行} catch (InterruptedException e) {System.out.println("线程在等待锁时被中断!");} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();System.out.println("线程释放锁...");}}});thread.start();// 等待 1 秒后中断线程TimeUnit.SECONDS.sleep(1);thread.interrupt(); // 中断线程return Result.ok("已尝试中断等待锁的线程");
}
2、两个线程竞争锁
  @GetMapping("/competeLockInterruptibly")@Operation(summary = "两个线程竞争锁,一个被中断")public Result competeLockInterruptibly() throws InterruptedException {RLock lock = redissonClient.getLock("competeInterruptLock");// 线程 A 先获取锁,并持有 10 秒Thread threadA = new Thread(() -> {try {lock.lock();System.out.println("线程 A 获取锁...");TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();System.out.println("线程 A 释放锁...");}});// 线程 B 尝试获取锁,但在等待过程中被中断Thread threadB = new Thread(() -> {try {System.out.println("线程 B 尝试获取锁...");lock.lockInterruptibly(); // 可中断加锁System.out.println("线程 B 获取锁...");} catch (InterruptedException e) {System.out.println("线程 B 在等待锁时被中断!");} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();System.out.println("线程 B 释放锁...");}}});threadA.start();TimeUnit.SECONDS.sleep(1); // 让线程 A 先加锁threadB.start();TimeUnit.SECONDS.sleep(2); // 让线程 B 等待 2 秒后中断threadB.interrupt(); // 中断线程 Breturn Result.ok("线程 B 被中断,未能获取锁");}

如果高优先级任务需要获取锁,可以 中断低优先级任务

16、lock.remainTimeToLive()

1、查询锁的剩余 TTL
@GetMapping("/lockTTL")
@Operation(summary = "获取锁的剩余过期时间")
public Result lockTTL() {RLock lock = redissonClient.getLock("testLock");lock.lock(30, TimeUnit.SECONDS); // 加锁 30 秒long ttl = lock.remainTimeToLive(); // 获取剩余过期时间System.out.println("锁的剩余存活时间:" + ttl + " 毫秒");return Result.ok("锁的剩余存活时间:" + ttl + " 毫秒");
}
2、动态续期锁
@GetMapping("/extendLockIfNeeded")
@Operation(summary = "如果锁快过期,则自动续期")
public Result extendLockIfNeeded() {RLock lock = redissonClient.getLock("dynamicLock");lock.lock(10, TimeUnit.SECONDS); // 10 秒自动过期long ttl = lock.remainTimeToLive();System.out.println("锁的剩余存活时间:" + ttl + " 毫秒");// 如果锁的剩余时间 < 3 秒,则自动续期if (ttl < 3000) {lock.lock(10, TimeUnit.SECONDS); // 再次续期 10 秒System.out.println("锁快过期,已自动续期 10 秒");}return Result.ok("锁的剩余时间:" + lock.remainTimeToLive() + " 毫秒");
}

注意:

自动续期仅适用于 lock() 和 lockInterruptibly() (默认无超时时间)

如果你手动指定了超时时间(lock(leaseTime, unit)),Redisson 不会自动续期

情况是否自动续期?原因
lock() 自动续期没有设置超时时间,Redisson 默认启用看门狗
lockInterruptibly() 自动续期和 lock() 一样
lock(10, TimeUnit.SECONDS)不会 自动续期手动指定了超时时间,Redisson 认为你不需要续期
tryLock(3, 10, TimeUnit.SECONDS)不会 自动续期设置了超时时间,Redisson 不会自动续期
3、检测死锁
@GetMapping("/detectDeadlockUsingTTL")
@Operation(summary = "检测死锁:如果锁的剩余 TTL 仍然很长,但任务已完成,则可能是死锁")
public Result detectDeadlockUsingTTL() {RLock lock = redissonClient.getLock("deadlockLock");lock.lock(20, TimeUnit.SECONDS);long ttl = lock.remainTimeToLive();if (ttl > 0) {System.out.println("检测到锁仍然存在,剩余 TTL:" + ttl + " 毫秒");if (ttl > 10000) { // 如果 TTL 仍然很长,但任务已完成,可能是死锁System.out.println("可能发生死锁,强制解锁...");lock.forceUnlock();return Result.ok("检测到死锁,已强制解锁");}}return Result.ok("未检测到死锁,锁剩余 TTL:" + ttl + " 毫秒");
}

相关文章:

架构设计之Redisson分布式锁-可重入同步锁(一)

架构设计之Redisson分布式锁-可重入同步锁(一) Redisson分布式锁官方博客地址 1、Redisson是什么 Redisson 是一个基于 Redis 的 Java 分布式工具库&#xff0c;它提供了 分布式锁、集合、队列、缓存、Map、限流、任务调度 等高级数据结构和功能&#xff0c;极大地简化了 Ja…...

使用libcurl编写爬虫程序指南

用户想知道用Curl库编写的爬虫程序是什么样的。首先&#xff0c;我需要明确Curl本身是一个命令行工具和库&#xff0c;用于传输数据&#xff0c;支持多种协议。而用户提到的“Curl库”可能指的是libcurl&#xff0c;这是一个客户端URL传输库&#xff0c;可以用在C、C等编程语言…...

【数据结构】排序算法(下篇·终结)·解析数据难点

前引&#xff1a;归并排序作为一种高效排序方法&#xff0c;掌握起来还是有点困难的&#xff0c;何况需要先接受递归的熏陶&#xff0c;这正是编程的浪漫之处&#xff0c;我们不断探索出新的可能&#xff0c;如果给你一串数据让其变得有序&#xff1f;是选择简单的冒泡、插入排…...

Django 使用 Celery 完成异步任务或定时任务

1 介绍 Celery是一个分布式任务队列&#xff0c;由三个主要组件组成&#xff1a;Celery worker、Celery beat 和消息代理&#xff08;例如 Redis 或 RabbitMQ&#xff09;。这些组件一起协作&#xff0c;让开发者能够轻松地执行异步任务和定时任务。 Celery worker&#xff1…...

Excel 自动执行全局宏

Excel 自动执行全局宏 25.04.09 步骤 1&#xff1a;创建个人宏工作簿&#xff08;Personal.xlsb&#xff09; 生成Personal.xlsb &#xff08;如尚未存在&#xff09;&#xff1a; 打开Excel → 开发工具 → 录制宏 → 选择“保存到个人宏工作簿” → 停止录制。按 Alt F11 进…...

【前缀和】矩阵区域和(medium)

矩阵区域和&#xff08;medium&#xff09; 题⽬描述&#xff1a;解法&#xff1a;代码Java 算法代码&#xff1a;C 算法代码&#xff1a; 题⽬描述&#xff1a; 题⽬链接&#xff1a;1314. 矩阵区域和 给你⼀个 m x n 的矩阵 mat 和⼀个整数 k &#xff0c;请你返回⼀个矩阵 …...

Android ViewStub显示VISIBLE与消失GONE,Kotlin

Android ViewStub显示VISIBLE与消失GONE&#xff0c;Kotlin import android.os.Bundle import android.util.Log import android.view.View import android.view.ViewStub import android.widget.Button import androidx.appcompat.app.AppCompatActivity import androidx.trac…...

【愚公系列】《高效使用DeepSeek》063-海关数据获取和管理

🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...

探索 OSPF 协议:构建高效网络的基石

文章目录 目录 文章目录 前言 一.OSPF协议概述 二.OSPF相关概念 &#x1f564; 2.1 基本思想 &#x1f564; 2.2 SPF算法 &#x1f564; 2.3 区域划分​编辑 三.OSPF工作原理 ​编辑 &#x1f564; 3.1 DR/BDR选举 四.OSPF网络类型 &#x1f564;4.1 BMA &#x1f564;4.2 P2P …...

深入剖析C++单例模式的八种实现演进与工程实践

深入剖析C单例模式的八种实现演进与工程实践 一、从基础到工业级&#xff1a;单例模式的演进图谱 1.1 基础实现的致命缺陷分析 // 初级版&#xff08;非线程安全&#xff09; class NaiveSingleton { public:static NaiveSingleton* getInstance() {if (!instance) {instanc…...

手游防DDoS攻击SDK接入

在手游中集成防DDoS攻击SDK是抵御流量型和应用层攻击的核心手段之一。以下从​​SDK选型、接入流程、防护策略优化​​三个维度提供完整指南&#xff0c;并附关键代码示例&#xff1a; ​​一、SDK选型与核心能力对比​​ ​​服务商​​​​优势​​​​劣势​​​​适用场景…...

【C++进阶】关联容器:multimap类型

目录 一、multimap 基础概念与底层实现 1.1 定义与核心特性 1.2 底层数据结构 1.3 类模板定义 1.4 与其他容器的对比 二、multimap 核心操作详解 2.1 定义与初始化 2.2 插入元素 2.3 查找元素 2.4 删除元素 2.5 遍历元素 三、性能分析与适用场景 3.1 时间复杂度分…...

学习threejs,使用EffectComposer后期处理组合器(采用RenderPass、FilmPass渲染通道)

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.EffectComposer 后期…...

用Django和AJAX创建一个待办事项应用

用Django和AJAX创建一个待办事项应用 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 用Django和AJAX创建一个待办事项应用让我们创建一个简单的 Django 项目,其中包含不同类型的 A…...

​【微调大模型】使用LLaMA-Factory进行监督微调 Qwen2.5 ​

本文使用LLaMA-Factory进行监督微调 Qwen2.5。 此监督微调&#xff08;SFT&#xff09;脚本具有以下特点&#xff1a; 支持单GPU和多GPU训练&#xff1b;支持全参数调优&#xff0c;LoRA&#xff0c;Q-LoRA&#xff0c;Dora。 下面详细介绍一下该脚本的使用方法。 目录 安装…...

前端 react+ant design ,后端 springboot +mysql+redis 全栈项目零基础小白从服务器初始化开始部署上线超详细保姆级教程

哈喽小伙伴们,好久不见,我是小李,今天,来电干货,希望对大家有帮助。 去年12月底的时候心血来潮,正好赶上腾讯云在做活动,就买了一台服务器,说是后面打算上线一两个项目,体验体验云服务器究竟是怎么玩的。后来由于实习和“冬招”,实在忙不过来了,就放在文件夹吃灰了…...

[Windows] OfficeAI 助手 v0.3.20(长期免费,2025-03-18 本地支持WPS_Word联动)

OfficeAI助手&#xff0c;作为Microsoft Office与WPS的得力智能插件&#xff0c;集文档自动生成、内容精准校对与润色、公式智能推荐等多功能于一体。它凭借强大的数据分析能力&#xff0c;深度融入Office/WPS办公生态&#xff0c;一键简化复杂流程&#xff0c;让办公效率倍增&…...

3DGS之光栅化

光栅化&#xff08;Rasterization&#xff09;是计算机图形学中将连续的几何图形&#xff08;如三角形、直线等&#xff09;转换为离散像素的过程&#xff0c;最终在屏幕上形成图像。 一、光栅化的核心比喻 像画家在画布上作画 假设你是一个画家&#xff0c;要把一个3D立方体画…...

可发1区的超级创新思路(python 、MATLAB实现):基于SAM+Informer+2DCNN的功率预测模型

首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 目录 首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 一、模型背景与核心创新 二、模型组件详解 1. SAM时空注意力模块 原理说明 代码实…...

【Java面试系列】Spring Boot微服务架构下的分布式事务解决方案与性能优化详解 - 3-5年Java开发必备知识

【Java面试系列】Spring Boot微服务架构下的分布式事务解决方案与性能优化详解 - 3-5年Java开发必备知识 1. 引言 在当今的微服务架构中&#xff0c;分布式事务是一个不可避免的话题。随着业务复杂度的提升&#xff0c;如何保证跨服务的数据一致性成为开发者和架构师必须面对…...

【MATLAB第114期】基于MATLAB的SHAP可解释神经网络分类模型(敏感性分析方法)

【MATLAB第114期】基于MATLAB的SHAP可解释神经网络分类模型&#xff08;敏感性分析方法&#xff09; 引言 该文章实现了一个可解释的神经网络分类模型&#xff0c;使用BP神经网络&#xff08;BPNN&#xff09;来预测特征输出。该模型利用12个变量参数作为输入特征进行训练。为…...

WPS免费使用宏(安装VBA插件)

WPS提示要开会员才能使用宏&#xff0c;多次搜索发现其实可以直接安装VBA插件就行&#xff0c;Mark一下 插件下载地址&#xff1a; https://www.onlinedown.net/soft/10044362.htm ‘’’ WPS插件软件介绍 wps vba是一款wps office插件&#xff0c;安装wps vba 7.1就可以让of…...

让测试飞起来——DevOps中的自动化测试实践指南

让测试飞起来——DevOps中的自动化测试实践指南 近年来,DevOps理念已经成为现代软件开发和运维的“最佳拍档”。它倡导“开发”和“运维”的协作,核心目标是加速交付,同时保障软件质量。而在这一过程中,测试自动化扮演了不可替代的角色。今天,我们就一起来聊聊测试自动化…...

开源AI大模型AI智能名片S2B2C商城小程序:科技浪潮下的商业新引擎

摘要&#xff1a; 本文聚焦于科技迅猛发展背景下&#xff0c;开源AI大模型、AI智能名片与S2B2C商城小程序的融合应用。通过分析元宇宙、人工智能、区块链、5G等前沿科技带来的商业变革&#xff0c;阐述开源AI大模型AI智能名片S2B2C商城小程序在整合资源、优化服务、提升用户体验…...

webpack配置导致浏览器自动刷新

文章目录 关键配置 - liveReload 关键配置 - liveReload const dev_config {devtool: source-map,// watch: true,devServer: {contentBase: path.resolve(__dirname, bin),port: 8005,host:192.168.xx.xx,inline: true,hot: false,liveReload: false //关键这一行【false不会…...

OPEX baota 2024.02.26

OPEX baota 2024.02.26 运维集成软件宝塔2024.02.26作废例子&#xff1a; 最重要的两个地方&#xff1a;上传文件 网站&#xff0c;重启应用服务器&#xff08;tomcat&#xff09; 其他很少用的...

【Pandas】pandas DataFrame to_numpy

Pandas2.2 DataFrame Conversion 方法描述DataFrame.astype(dtype[, copy, errors])用于将 DataFrame 中的数据转换为指定的数据类型DataFrame.convert_dtypes([infer_objects, …])用于将 DataFrame 中的数据类型转换为更合适的类型DataFrame.infer_objects([copy])用于尝试…...

Tensorflow2实现: LSTM-火灾温度预测

- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/rnFa-IeY93EpjVu0yzzjkw) 中的学习记录博客** - **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** 一&#xff1a;理论知识基础 1.LSTM原理 一句话介…...

【降尺度】AI+CMIP6数据分析与可视化、降尺度技术与气候变化的区域影响、极端气候分析

气候变化已成为全球性挑战&#xff0c;对农业、生态系统、水资源、人类健康和社会经济系统产生深远影响。科学研究表明&#xff0c;自工业革命以来&#xff0c;人类活动导致的温室气体排放与全球气温上升、极端天气事件增加、冰川融化和海平面上升等现象密切相关。为科学理解和…...

粒子系统优化完成

按计划对幻世&#xff08;OurDream&#xff09;2D图形引擎的粒子系统进行了加强和优化&#xff0c;重点强化了粒子运动的控制和颜色混合效果的功能&#xff0c;目前优化过后的粒子系统的整体效果是令人满意的。...

spark-core编程

RDD转换算子 RDD 的两种算子&#xff1a;转换算子和行动算子。 RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value 类型。 算子实际上是一些函数&#xff0c;用于数据处理。 Value类型 map 将处理的数据逐条进行映射转换&#xff0c;…...

智慧班牌系统解决方案,SaaS智慧电子班牌云平台

智慧班牌系统解决方案 系统概述 智慧班牌是智慧校园建设不断发展的产物&#xff0c;是教育信息化改革的载体。通过智慧班牌可以高效便捷传递各种知识信息和通知信息、及时反馈课堂信息、实现班级的透明化管理。智慧班牌将学生平安考勤、异常出勤情况及时反馈至家长、老师&…...

Flutter 2025 Roadmap

2025 这个路线图是有抱负的。它主要代表了我们这些在谷歌工作的人收集的内容。到目前为止&#xff0c;非Google贡献者的数量超过了谷歌雇佣的贡献者&#xff0c;所以这并不是一个详尽的列表&#xff0c;列出了我们希望今年Flutter能够出现的所有令人兴奋的新事物&#xff01;在…...

【开发工具】科研开发中的主流AI工具整理及如何使用GPT润色英文论文

一、主流AI工具 AI技术发展至今已经逐渐成熟&#xff0c;并可以取代一部分科研和开发中的简单工作&#xff0c;并为复杂工作提高辅助&#xff0c;除此之外也是更高级的信息检索工具。熟练掌握 AI 工具在当前市场理应具有竞争优势&#xff0c;目前笔者在科研和开发中接触过AI工…...

用excel做九乘九乘法表

公式&#xff1a; IF($A2>B 1 , 1, 1,A2 & “" & B$1 & “” & $A2B$1,”")...

nacos配置达梦数据库驱动源代码步骤

1.在父工程pom.xml添加依赖&#xff1a; <dependency><groupId>com.dameng</groupId><artifactId>DmJdbcDriver18</artifactId><version>8.1.1.193</version> </dependency> 2.在nacos-config模块pom.xml添加依赖&#xff1…...

Spring Boot 线程池配置详解

Spring Boot 线程池配置详解 一、核心配置参数及作用 基础参数核心线程数 (corePoolSize)‌ 作用‌:线程池中始终保持存活的线程数量,即使空闲也不回收‌。 建议‌:根据任务类型设定(如 I/O 密集型任务可设为 CPU 核心数 2)‌。 最大线程数 (maxPoolSize)‌ 作用‌:…...

如何使用 qrcode.react生成二维码

qrcode.react&#xff08;查看官网&#xff09; 是一个用于 React 应用的 QR 码生成组件。下面是如何使用它的详细指南&#xff1a; 1、安装 npm install qrcode.react # 或者 yarn add qrcode.react2、基本用法 import {QRCodeSVG} from qrcode.react;const myPage () >…...

用VScode来编写前后端——构建基础框架

前言 我写这一个板块的原因是我参加了我们学校的新生项目课&#xff0c;需要创立一个系统&#xff0c;我们小组选的标题的基于计算机视觉的商品识别系统&#xff0c;那么我们需要一个网站来展示我们的功能&#xff0c;故写这些来记录一下自己&#xff0c;大家如果有什么问题的话…...

23.OpenCV轮廓逼近与拟合

OpenCV轮廓逼近与拟合 在计算机视觉中&#xff0c;轮廓是图像中边界或形状的重要表达形式。然而&#xff0c;直接从图像中提取的轮廓常常包含大量冗余点&#xff0c;且噪声较多。为了更好地描述图像中的形状&#xff0c;我们通常需要对轮廓进行逼近和拟合&#xff0c;从而降低…...

Flutter Row / Column 组件详解

1. 引言 在 Flutter 中&#xff0c;Row 和 Column 是最常用的布局组件&#xff0c;用于在水平方向 (Row) 或垂直方向 (Column) 排列子组件。它们提供了强大的对齐方式、空间分配策略&#xff0c;适用于各种 UI 设计需求。本文将详细介绍它们的基本用法、主要属性及自定义样式。…...

WHAT - 表单场景 - 依赖联动

目录 示例场景技术栈示例代码功能点总结详情场景 - 依赖联动初始化示例说明&#xff1a;详情页场景&#xff08;含回显、联动&#xff09;修改点说明示例代码&#xff08;详情页&#xff09;总结一下关键点 下面是一个基于 React TypeScript Ant Design (antd) 的表单联动示例…...

SecProxy - 自动化安全协同平台

本人为甲方安全人员&#xff0c;从事甲方工作近6年&#xff1b;针对在甲方平时安全工作的一些重复、复杂、难点的工作&#xff0c;思考如何通过AI、脚本、或者工具实现智能且自动化&#xff0c;于是花平时空闲时间准备将这些能力全部集中到一个平台&#xff0c;于是有了这个东西…...

网络3 子网掩码 划分ip地址

1.根据子网掩码判断主机数 IP地址网络位主机位 核心&#xff1a;将主机位划分为子网位和主机位 疑问&#xff1a;子网位有什么作用 子网掩码&#xff1a;网络位全为1&#xff0c;主机位全为0 主机数2^主机位 -2 2.根据主机和子网判断子网掩码 有一个B类网络145.38.0.0需要划…...

电容详解:定义、作用、分类与使用要点

一、电容的基本定义 电容&#xff08;Capacitor&#xff09; 是由两个导体极板&#xff08;正负极&#xff09;和中间绝缘介质组成的储能元件&#xff0c;其基本特性为存储电荷。 公式&#xff1a; C Q / V C&#xff1a;电容值&#xff08;单位&#xff1a;法拉F&#xff09…...

Sublime Text for Mac v4.0 【注册汉化版】代码编辑器

Sublime Text for Mac v4.0 【注册汉化版】代码编辑器 一、介绍 sublime text for Mac一款轻量级的文本编辑器&#xff0c;拥有丰富的功能和插件。它支持多种编程语言&#xff0c;包括C、Java、Python、Ruby等&#xff0c;可以帮助程序员快速编写代码。Sublime Text的界面简洁…...

OpenCV 进阶实战与技巧——图像处理的全面解析

在上篇文章中&#xff0c;我们一起迈入了 Python OpenCV 的奇妙世界&#xff0c;学习了图像的读取、显示和保存等基本操作&#xff1a;用Python和OpenCV开启图像处理魔法之旅-CSDN博客。今天&#xff0c;我们将继续深入&#xff0c;探索图像的各种变换、滤波、边缘检测以及更深…...

单细胞转录组-生物标志物篇 | 从异质性描绘到筛查应用

宫颈癌&#xff08;Cervical cancer, CC&#xff09;是一种常见的妇科恶性肿瘤。尽管目前的筛查方法已被证明有效并显著降低了CC的发病率和死亡率&#xff0c;但仍然存在缺陷。单细胞RNA测序可以在单细胞分辨率下鉴定复杂和稀有的细胞群。通过scRNA-seq&#xff0c;已经绘制和描…...

MQTT:深入剖析 paho.mqtt.embedded - c - master 目录结构

引言 在嵌入式开发领域&#xff0c;消息队列遥测传输&#xff08;MQTT&#xff09;协议因其轻量级、低带宽消耗等特性被广泛应用。Eclipse Paho 项目提供了多种语言的 MQTT 客户端实现&#xff0c;其中paho.mqtt.embedded - c - master是针对 C 语言在嵌入式环境下的 MQTT 客户…...

minio命令行客户端mc常见用法

安装minio命令行客户端mc https://min-io.cn/docs/minio/linux/reference/minio-mc-admin.html # Windows安装minio命令行客户端 choco install minio-client -y# Linux安装mc客户端 wget -c -P /usr/local/bin/ https://dl.min.io/client/mc/release/linux-amd64/mc # 赋予可…...