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

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
H
Hacker News: Front Page
Hacker News - Newest:
Hacker News - Newest: "LLM"
L
Lohrmann on Cybersecurity
Cisco Talos Blog
Cisco Talos Blog
O
OpenAI News
S
Securelist
Security Latest
Security Latest
T
Threat Research - Cisco Blogs
H
Heimdal Security Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recorded Future
Recorded Future
Microsoft Azure Blog
Microsoft Azure Blog
MyScale Blog
MyScale Blog
Webroot Blog
Webroot Blog
The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
Latest news
Latest news
N
Netflix TechBlog - Medium
N
News and Events Feed by Topic
D
Docker
D
DataBreaches.Net
A
About on SuperTechFans
T
Tor Project blog
V
V2EX
G
Google Developers Blog
博客园 - Franky
N
News | PayPal Newsroom
T
The Blog of Author Tim Ferriss
I
InfoQ
H
Help Net Security
V2EX - 技术
V2EX - 技术
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Security Affairs
SecWiki News
SecWiki News
The Register - Security
The Register - Security
人人都是产品经理
人人都是产品经理
NISL@THU
NISL@THU
小众软件
小众软件
B
Blog
T
Threatpost
P
Palo Alto Networks Blog
博客园 - 【当耐特】
L
LangChain Blog
AWS News Blog
AWS News Blog
月光博客
月光博客
宝玉的分享
宝玉的分享

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

用一个实际业务场景演示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 的锁。​

--