记一个,生产遇到的redission锁,释放问题:lock.tryLock(0, 0, TimeUnit.SECONDS)

package com.aswatson.cdc.test;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.time.LocalDateTime;
import java.util.concurrent.*;

/**
 *  boolean success = lock.tryLock(0, 0, TimeUnit.SECONDS); // 表示尝试获取锁,等待0秒,持有锁0秒钟
 *  注意问题,存在的隐患: 虽然 tryLock(0, 0, TimeUnit.SECONDS)
 *
 *  首先1. 但实际锁的释放仍然会受到 Redisson 看门狗机制的影响。如果持有锁的线程未能在续约周期内续约锁的持有时间,那么锁可能会在超时后被自动释放。
 *  (默认是每隔 30 秒进行一次续约)来维持锁的有效性,避免因为持有锁的线程未能释放而造成锁的永久占用。或者自己unLock。
 *
 *  其次2. 确保你使用的 Redisson 版本与 Redis 版本兼容,并且不会因为版本问题导致锁的行为异常。目前测试用的是redis(2.7.17)、redisson(3.24.3)
 *
 *  其次3. 默认情况下,Redisson 的看门狗会定期发送续约请求给 Redis 服务器,以延长当前持有的锁的有效期。但是也有不会续约的可能性:
 *         Redis 连接中断、Redisson 配置问题、持有锁的线程崩溃、锁的最大持有时间到期。
 *
 *  其次4.  即使在业务逻辑中调用了阻塞操作(如 sleep),Redisson 也会在后台继续进行续约操作,以防止锁被意外释放。
 *
 */
public class TestRedissonLeaveTimeLock {

    public static void main(String[] args) throws Exception {

        Config config = new Config();
        config.useSingleServer().setAddress("redis://10.95.35.93:37495");
        RedissonClient redissonClient = Redisson.create(config);
        RLock lock = redissonClient.getLock("lockName");
        System.out.println("创建好了RedissonClient" + getName());

        int numThreads = 10;
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch doneLatch = new CountDownLatch(numThreads);

        // 尽管 for 循环看起来是按顺序逐个,但实际上每个任务会并发地在后台执行。
        // 这是因为每次调用 submit时,任务被提交给线程池,而线程池会根据可用的线程资源并发执行这些任务。
        for (int i = 0; i < numThreads; i++) {
            executor.submit(() -> {
                try {
                    startLatch.await(); // 等待主线程的启动信号

                    System.out.println("获取锁前的时间:"+ getName());
                    boolean success = lock.tryLock(0, 0, TimeUnit.SECONDS); // 尝试获取锁,等待0秒,持有锁0秒钟
                    System.out.println("获取锁后的时间:"+ getName());
                    if (success) {
                        System.out.println("拿到锁"+ getName());
                        // 模拟业务处理耗时 大于锁过期,可能导致非自己持有的锁被释放。
                        TimeUnit.SECONDS.sleep(60);
                    } else {
                        System.out.println("未能获取到锁,已放弃尝试" + getName());
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    doneLatch.countDown();// 每次减去1

                    // 判断当前线程是否持有锁
                    if (lock.isHeldByCurrentThread()) {
                        System.out.println("释放锁"+ getName());
                        lock.unlock();
                    }
                }
            });
        }

        System.out.println("主线程即将释放所有等待的线程...");
        startLatch.countDown(); // 释放定义的1条线程,开始并发执行
        doneLatch.await(); // 等待所有线程10条完成
        executor.shutdown();
        System.out.println("所有线程执行完成" + getName());
    }

    public static String getName() {
        return Thread.currentThread().getName() + "---" + LocalDateTime.now();
    }
   //目前测试结果: 同时并发10条,9条拿不到锁,1条拿到锁,而且不会自动释放锁,即便LeaveTime = 0, 而且线程睡了60s,超过
   //看门狗机制的等待时间,也不会自动释放,而是打印了日志: 释放锁... 所以调用了阻塞操作(如 sleep),Redisson 也会在后台继续进行续约操作,以防止锁被意外释放。

}
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.17</version>
    <relativePath/>
  </parent>

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      <version>2.7.17</version>
    </dependency>
    <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson-spring-data-27</artifactId>
      <version>3.24.3</version>
    </dependency>
    <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson-spring-boot-starter</artifactId>
      <version>3.24.3</version>
    </dependency>

下面分析一下Redisson分布式锁源码:(注意版本差役,可能效果不一样)

 lock() 和 tryLock()都会用的 tryAcquireAsync(),看到区别,其实核心是leaseTime是否 > 0 

leaseTime不大于0时,会执行scheduleExpirationRenewal(threadId);

 当oldEntry == null时,执行renewExpiration(); 就是个定时器,即是看门狗核心逻辑了。

 其中有下面红圈这段逻辑,也就是看门狗之所以在三分之一的时候执行的原因, 没有自定义时

 config.setLockWatchdogTimeout(10000); // 设置锁的看门狗超时时间为10秒

默认就是30秒 除以3  也就是10秒时执行。

posted @ 2024-06-27 16:37  威兰达  阅读(579)  评论(0编辑  收藏  举报