Redisson分布式锁实战:深入解析lock()与tryLock()的阻塞与非阻塞机制

1. 从单机到集群:为什么我们需要分布式锁?

记得我刚入行那会儿,做的还是单体应用。那时候处理多线程并发问题,脑子里蹦出来的第一个词就是 synchronized,或者用 ReentrantLock。这招在单机环境下确实好使,锁住一个共享变量,大家排队访问,数据安全得很。

但后来项目越做越大,服务从一台机器变成了十几台、几十台机器组成的集群。问题就来了:我在机器A上用 synchronized 锁住了一个对象,机器B上的线程压根就不知道这回事,它依然可以大摇大摆地访问共享资源(比如数据库里的库存)。这下好了,数据一致性彻底乱套,超卖、重复扣款这些生产事故就跟着来了。我踩过的坑告诉我,单机锁在分布式系统面前,就像用一把锁去锁一排并排的门,根本锁不住。

这时候,分布式锁 就成了我们的“救命稻草”。它的核心思想很简单:我们需要一个所有服务节点都能访问到的、全局唯一的“裁判”,来裁决同一时刻谁能拿到锁。这个“裁判”通常是一个独立的中间件,比如 Redis 或者 ZooKeeper。今天咱们的主角 Redisson,就是基于 Redis 实现分布式锁的一个非常优秀的 Java 客户端库,它把复杂的分布式锁逻辑封装成了像使用本地锁一样简单的 API。

在 Redisson 的武器库里,最常用、也最让人纠结的两个方法就是 lock()tryLock()。一个会“傻等”,一个会“试探”。选错了,轻则性能下降,重则系统死锁。接下来,我就结合电商秒杀、库存扣减这些咱们程序员天天打交道的场景,把这哥俩的脾气秉性给你掰扯清楚。

2. 死等到底:深入 Redisson 的 lock() 方法

lock() 这个方法,我给它起的外号叫“老实人”。它的行为非常直接:一旦决定要锁,就死等到底,直到拿到锁为止。这种模式在学术上被称为 阻塞式加锁

2.1 lock() 是怎么工作的?

我们先来看一段最简单的使用代码。说实话,在实际项目里我直接用 lock() 的场景不多,但为了弄明白它,我特意写了测试代码来折腾它。

@Autowired
private RedissonClient redissonClient;

public void doSomething() {
    RLock lock = redissonClient.getLock("myLock");
    // 获取锁,如果锁被占用,当前线程会一直等待
    lock.lock();
    try {
        // 你的核心业务逻辑,比如扣减库存
        System.out.println("执行业务逻辑...");
        Thread.sleep(5000); // 模拟耗时操作
    } finally {
        // 必须在finally块中释放锁
        lock.unlock();
    }
}

你也可以指定一个租约时间,意思是“我最多只持有锁这么长时间,时间一到,即使我没干完活,锁也自动释放,防止我死锁”。

// 获取锁,并设置锁的自动释放时间为10秒
lock.lock(10, TimeUnit.SECONDS);

这里有个大坑我得提醒你:如果你用了带超时参数的 lock(leaseTime, unit),Redisson 会关闭看门狗自动续期机制。这意味着,如果你的业务逻辑执行时间超过了 10 秒,锁会在第10秒自动释放,而此时你的业务可能还没执行完!其他线程就能拿到锁进来操作,导致数据错乱。所以,除非你非常确定业务的最大执行时间,否则慎用这个参数,或者使用我们后面会讲的 tryLock

2.2 源码窥探:lock() 的“死循环”哲学

光会用不行,咱们还得看看它肚子里装的什么货。扒开 lock() 的源码(这里我做了大量简化,只留核心逻辑),你会发现它的核心就是一个“尝试-等待-再尝试”的循环。

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    long threadId = Thread.currentThread().getId();
    // 第一次尝试获取锁
    Long ttl = tryAcquire(-1L, leaseTime, unit, threadId);
    if (ttl == null) {
        // 获取成功,直接返回
        return;
    }
    // 获取失败,订阅这个锁的释放消息
    CompletableFuture<RedissonLockEntry> future = subscribe(threadId);
    try {
        // 进入“死循环”,直到获取锁成功
        while (true) {
            // 再次尝试获取锁
            ttl = tryAcquire(-1L, leaseTime, unit, threadId);
            if (ttl == null) {
                // 成功,跳出循环
                return;
            }
            // 如果锁被其他线程持有(ttl是剩余的存活时间)
            if (ttl >= 0) {
                // 这里很关键!它不是无脑循环,而是利用信号量(Semaphore)等待
                // 最多等待 ttl 毫秒,等锁持有者释放的信号
                future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } else {
                // 如果ttl为负(一种特殊情况),则进行不可中断的等待
                future.getNow().getLatch().acquire();
            }
        }
    } finally {
        unsubscribe(future, th
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值