惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
爱范儿
爱范儿
H
Help Net Security
Last Week in AI
Last Week in AI
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
B
Blog
C
Check Point Blog
T
Tailwind CSS Blog
云风的 BLOG
云风的 BLOG
D
Docker
Recent Announcements
Recent Announcements
Vercel News
Vercel News
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
MyScale Blog
MyScale Blog
The GitHub Blog
The GitHub Blog
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
月光博客
月光博客
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
The Register - Security
The Register - Security
V
Visual Studio Blog
F
Full Disclosure
Hugging Face - Blog
Hugging Face - Blog
T
Threat Research - Cisco Blogs
Latest news
Latest news
PCI Perspectives
PCI Perspectives
Cisco Talos Blog
Cisco Talos Blog
博客园 - Franky
D
DataBreaches.Net
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
Google Developers Blog
P
Palo Alto Networks Blog
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
T
Tenable Blog
L
LINUX DO - 热门话题
Spread Privacy
Spread Privacy

博客园 - 有点懒惰的大青年

用一个实际业务场景演示ON和WHERE的正确使用方式 idea中查看源码 异或运算 使用Hutool对时间的花式操作 合并K个升序列表 比较器 枚举类的设计模式 idea同时启动application,启用不同端口 @PostConstruct用法 ApplicationContext 事件发布与监听机制详解 java自带命令jps介绍 关于maven中标签的说明 mysql的跨库查询 linux命令ll显示结果的含义 Files类的使用 java环境变量设置 java值传递和引用传递 kafka的集群与可靠性 kafka的消费全流程 关于kafka 达梦数据库执行计划介绍 关于MQ 用位运算实现加减乘除(3)
spring boot中使用RedissonClient实现分布式锁
有点懒惰的大青年 · 2026-03-01 · via 博客园 - 有点懒惰的大青年

 1.参考文章:  

https://developer.aliyun.com/article/952621

https://www.cnblogs.com/architectforest/p/13140352.html
https://blog.csdn.net/hugejiletuhugejiltu/article/details/149248846

2.需要引入的依赖为:

<properties>
    <redisson.version>3.20.1</redisson.version>
</properties>

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>${redisson.version}</version>
</dependency>

3.本地部署的redis为集群配置,所以在application.yml中添加配置:

spring:
  redis:
    cluster:
      nodes: 192.168.201.66:7001,192.168.201.66:7002,192.168.201.66:7003,192.168.201.66:7004,192.168.201.66:7005,192.168.201.66:7006
    password: csii@2021

4.RedisService实现分布式锁的实现类:

package com.csii.service;

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;

/**
 * @description com.csii.service
 * @author: chengyu
 * @date: 2026-03-01 14:18
 */
@Service
@Slf4j
public class RedisService {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 获取锁
     * 如果锁不可用,则当前线程将无法参与线程调度,并处于休眠状态,直至获取该锁。
     * 实现注意事项 锁的实现可能能够检测到锁的错误使用,例如可能导致死锁的调用,并可能在这种情况下抛出(未检查的)异常。
     * 该锁实现必须记录这些情况和异常类型。
     */
    public void lock(String lockKey) {
        try {
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock();
        } catch (Exception e) {
            log.error("redis service lock error", e);
            throw e;
        }
    }

    /**
     * 尝试获取锁
     * 仅在调用时锁处于空闲状态时才获取该锁。
     * 如果锁可用,则获取该锁并立即返回true值。如果锁不可用,则此方法将立即返回false值。
     *
     * @param lockKey
     * @return 返回值:若已获取锁,则返回true;否则返回false
     */
    public boolean tryLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.tryLock();
    }

    /**
     * 尝试获取锁,给定一个等待时间
     * 如果在给定的等待时间内锁处于空闲状态,且当前线程未被中断,则获取该锁。
     *
     * @param lockKey
     * @param waitTime 等待时间,单位s
     *
     * @return 如果获取了锁,则返回值为true。 如果获取锁的过程中等待且指定的等待时间已过,则返回值为false
     */
    public boolean tryLock(String lockKey, long waitTime) {
        try {
            RLock lock = redissonClient.getLock(lockKey);
            return lock.tryLock(waitTime, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("redis service trylock error", e);
        }
        return false;
    }

    /**
     * 释放锁
     *
     * @param lockKey
     * @return
     */
    public boolean unlock(String lockKey) {
        RLock rLock = redissonClient.getLock(lockKey);
        if (rLock != null && rLock.isLocked()) {
            rLock.unlock();
            return true;
        }
        return false;
    }
}

5.测试redis分布式锁的Controller:

package com.csii.controller;

import com.csii.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @description 测试redis分布式锁Controller
 * @author: chengyu
 * @date: 2026-03-01 14:50
 */
@RestController
@Slf4j
public class TestLockController {

    @Autowired
    private RedisService redisService;

    private static final String LOCK_KEY = "LOCK:EXECUTE:METHOD";

    @GetMapping("/testLock")
    public void callService() {
        Thread t1 = new Thread(() -> method2(), "thread1");
        Thread t2 = new Thread(() -> method2(), "thread2");
        Thread t3 = new Thread(() -> method2(), "thread3");

        t1.start();
        t2.start();
        t3.start();
    }

    /**
     * 尝试获取到锁才执行,给定一个等待获取锁的时间(2分钟)
     */
    public void method2() {
        try {
            boolean acquire = redisService.tryLock(LOCK_KEY, 120);
            if (!acquire) {
                log.info("redis service try lock failed, method not execute and quit.");
                return;
            }

            log.info("method {} begin execute...", Thread.currentThread().getName());
            Thread.sleep(10000);
            log.info("method {} execute over", Thread.currentThread().getName());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            redisService.unlock(LOCK_KEY);
        }
    }

    /**
     * 获取到锁才执行
     */
    public void method() {
        try {
            redisService.lock(LOCK_KEY);
            log.info("method {} begin execute...", Thread.currentThread().getName());
            Thread.sleep(20000);
            log.info("method {} execute over", Thread.currentThread().getName());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            redisService.unlock(LOCK_KEY);
        }
    }
}

6.查看执行结果:

2026-03-01 16:10:48.784  INFO 24120 --- [        thread1] com.csii.controller.TestLockController   : method thread1 begin execute...
2026-03-01 16:10:58.786  INFO 24120 --- [        thread1] com.csii.controller.TestLockController   : method thread1 execute over
2026-03-01 16:10:58.798  INFO 24120 --- [        thread2] com.csii.controller.TestLockController   : method thread2 begin execute...
2026-03-01 16:11:08.799  INFO 24120 --- [        thread2] com.csii.controller.TestLockController   : method thread2 execute over
2026-03-01 16:11:08.810  INFO 24120 --- [        thread3] com.csii.controller.TestLockController   : method thread3 begin execute...
2026-03-01 16:11:18.811  INFO 24120 --- [        thread3] com.csii.controller.TestLockController   : method thread3 execute over

可以看到是有序执行的。

因为方法的执行时间是10s,这里如果把获取锁的等待时间改为1s,则会报错,因为获取锁失败了:

查看代码
2026-03-01 16:12:36.951  INFO 24120 --- [        thread2] com.csii.controller.TestLockController   : method thread2 begin execute...
2026-03-01 16:12:37.946  INFO 24120 --- [        thread1] com.csii.controller.TestLockController   : redis service try lock failed, method not execute and quit.
2026-03-01 16:12:37.946  INFO 24120 --- [        thread3] com.csii.controller.TestLockController   : redis service try lock failed, method not execute and quit.
Exception in thread "thread3" Exception in thread "thread1" java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 2ba0d98b-a983-4fc0-9b0b-1d7feb4b6a29 thread-id: 463
	at org.redisson.RedissonBaseLock.lambda$unlockAsync0$6(RedissonBaseLock.java:369)
	at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:822)
	at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:797)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
	at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1962)
	at org.redisson.command.CommandBatchService.lambda$executeAsync$7(CommandBatchService.java:301)
	at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:760)
	at java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:736)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
	at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1962)
	at org.redisson.command.RedisCommonBatchExecutor.handleResult(RedisCommonBatchExecutor.java:163)
	at org.redisson.command.RedisExecutor.checkAttemptPromise(RedisExecutor.java:552)
	at org.redisson.command.RedisExecutor.lambda$execute$5(RedisExecutor.java:195)
	at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:760)
	at java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:736)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
	at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1962)
	at org.redisson.client.handler.CommandDecoder.decodeCommandBatch(CommandDecoder.java:325)
	at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:217)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:144)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:120)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:519)
	at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:280)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 2ba0d98b-a983-4fc0-9b0b-1d7feb4b6a29 thread-id: 461
	at org.redisson.RedissonBaseLock.lambda$unlockAsync0$6(RedissonBaseLock.java:369)
	at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:822)
	at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:797)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
	at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1962)
	at org.redisson.command.CommandBatchService.lambda$executeAsync$7(CommandBatchService.java:301)
	at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:760)
	at java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:736)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
	at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1962)
	at org.redisson.command.RedisCommonBatchExecutor.handleResult(RedisCommonBatchExecutor.java:163)
	at org.redisson.command.RedisExecutor.checkAttemptPromise(RedisExecutor.java:552)
	at org.redisson.command.RedisExecutor.lambda$execute$5(RedisExecutor.java:195)
	at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:760)
	at java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:736)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
	at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1962)
	at org.redisson.client.handler.CommandDecoder.decodeCommandBatch(CommandDecoder.java:325)
	at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:217)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:144)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:120)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:519)
	at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:280)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)
2026-03-01 16:12:46.953  INFO 24120 --- [        thread2] com.csii.controller.TestLockController   : method thread2 execute over

7.需要注意事项:

在使用 Redisson 分布式锁时,需注意以下事项,以避免潜在问题:​

1.锁的粒度设计​
锁的粒度应尽可能小,即只锁定需要保护的资源,避免大范围锁定导致并发性能下降。例如,在库存扣减场景中,应按商品 ID 锁定(stock:deduct:1001),而不是锁定整个库存表(stock:deduct),否则不同商品的库存扣减会相互阻塞。​

2.避免死锁​
尽管 Redisson 的过期机制和 Watch Dog 可减少死锁风险,但仍需注意:​
确保在finally块中释放锁,避免业务逻辑异常导致锁未释放。​
避免在持有锁的情况下调用外部服务(如 HTTP 请求、消息发送),若外部服务响应缓慢,可能导致锁超时释放。​
合理设置锁的持有时间和等待时间,避免线程长时间等待锁或持有锁过久。​

3.锁的名称规范​
锁的名称应具有唯一性和可读性,建议采用 “业务模块:操作:资源 ID” 的格式(如order:create:10001表示订单创建锁,资源 ID 为订单号),便于问题排查和监控。

4.避免锁的误释放​
解锁时必须先检查当前线程是否持有锁(lock.isHeldByCurrentThread()),否则可能释放其他线程持有的锁。例如,若线程 A 获取锁后因超时被释放,线程 B 获取锁,此时线程 A 若未检查直接解锁,会错误释放线程 B 的锁。​

--