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

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

Redisson分布式锁官方博客地址

1、Redisson是什么

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

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

1、独占性

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

2、高可用

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

3、防死锁

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

4、不乱抢

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

5、重入性

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

3、Redisson 锁相关的API

java 复制代码
// 获取一个可重入的分布式锁(普通锁)
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 复制代码
<?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

yaml 复制代码
server:
  port: 8001
  servlet:
    context-path: /redisson

spring:
  data:
    redis:
      database: 0
      host: 127.0.0.1
      password: 123456
      port: 6379

3、启动类

java 复制代码
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 统一返回

java 复制代码
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 配置类

java 复制代码
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;

    @Bean
    public RedissonClient getRedissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://" + host + ":" + port + "")
                .setDatabase(database)
                .setPassword(password);
        return Redisson.create(config);
    }

}

6、Controller

java 复制代码
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 {

    @Resource
    private RedissonClient redissonClient;

}

5、可重入的分布式锁getLock

1、lock() 无参数

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

java 复制代码
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 秒后自动释放锁 ,适用于 避免死锁 的场景。

java 复制代码
/**
 * 方式 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() 无参数

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

java 复制代码
  /**
   * 方式 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) 带最大等待时间 推荐

java 复制代码
/**
 * 方式 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) 带超时时间 推荐

java 复制代码
/**
 * 方式 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、释放锁的正确写法

java 复制代码
if (lock.isHeldByCurrentThread()) { // 确保当前线程持有锁
    lock.unlock(); // 释放锁
}
java 复制代码
// 避免出现以下错误
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 3dbb3527-6e66-4cb9-8837-810a6ba39da0 thread-id: 540
	at 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()

java 复制代码
/**
 * 检查当前线程是否持有锁
 */
@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()

java 复制代码
/**
 * 获取当前线程持有的锁次数
 */
@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、递归调用加锁
java 复制代码
/**
 * 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、父子方法加锁
java 复制代码
/**
 * 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、多层业务逻辑加锁
java 复制代码
/**
 * 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、确保锁完整释放
java 复制代码
/**
 * 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()

检查指定线程是否持有锁

java 复制代码
@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()); // true
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    });

    thread.start();

    // 等待子线程获取锁
    TimeUnit.SECONDS.sleep(1);

    // 获取子线程 ID
    long threadId = thread.getId();

    // 判断子线程是否持有锁
    boolean isHeld = lock.isHeldByThread(threadId);
    System.out.println("主线程检查子线程是否持有锁:" + isHeld);

    return Result.ok("子线程是否持有锁:" + isHeld);
}

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

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

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

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

java 复制代码
@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、防止错误释放其他线程的锁

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

java 复制代码
@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、定期检查某个任务是否被锁定

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

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

java 复制代码
@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) 检测 某个线程是否一直持有锁 ,如果超过一定时间仍未释放,则可以 手动解锁

java 复制代码
@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、普通可中断加锁
java 复制代码
@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、两个线程竞争锁
java 复制代码
  @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(); // 中断线程 B

      return Result.ok("线程 B 被中断,未能获取锁");
  }

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

16、lock.remainTimeToLive()

1、查询锁的剩余 TTL
java 复制代码
@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、动态续期锁
java 复制代码
@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、检测死锁
java 复制代码
@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 + " 毫秒");
}
相关推荐
我命由我1234531 分钟前
35.Java线程池(线程池概述、线程池的架构、线程池的种类与创建、线程池的底层原理、线程池的工作流程、线程池的拒绝策略、自定义线程池)
java·服务器·开发语言·jvm·后端·架构·java-ee
CopyLower1 小时前
分布式ID生成方案的深度解析与Java实现
java·开发语言·分布式
m0_684598535 小时前
如何开发英语在线训练小程序:从0到1的详细步骤
java·微信小程序·小程序·小程序开发
Charlie__ZS5 小时前
Redis-事务
数据库·redis·缓存
ml130185288745 小时前
开发一个环保回收小程序需要哪些功能?环保回收小程序
java·大数据·微信小程序·小程序·开源软件
Charlie__ZS5 小时前
SpringCloud - 分布式事务
分布式·spring·spring cloud
Charlie__ZS5 小时前
Redis-数据类型
数据库·redis·缓存
神奇小永哥5 小时前
redis之缓存击穿
数据库·redis·缓存
zybishe6 小时前
免费送源码:Java+ssm+MySQL 酒店预订管理系统的设计与实现 计算机毕业设计原创定制
java·大数据·python·mysql·微信小程序·php·课程设计
anlogic7 小时前
Java基础 4.12
java·开发语言