当前位置: 首页 > article >正文

模拟Redisson获取锁 释放锁 锁续命

获取锁


Long timeout = 3000L;//获取锁超时时间
Map<Thread, RedisLockInfo> lockCacheMap = new ConcurrentHashMap<>();//本地缓存锁

public boolean tryLock() {
        //获取当前线程
        Thread cuThread = Thread.currentThread();
    	//先从本地缓存中获取锁实例
        RedisLockInfo redisLockInfo = lockCacheMap.get(cuThread);
        if (redisLockInfo != null && redisLockInfo.isState()) {
            // 这把锁可重入次数+1
            redisLockInfo.reentry++;
            log.info("<您在之前已经获取过锁,锁直接可重入>");
            return true;
        }
        
        Long startTime = System.currentTimeMillis();
        Long expire = 30000L; //过期时间
        String lockId = UUID.randomUUID().toString(); //锁唯一id
        for (; ; ) {
            Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, lockId, expire, TimeUnit.SECONDS);
            if (lock) {
                log.info("<获取锁成功>");
                RedisLockInfo newRedisLockInfo = new RedisLockInfo(lockId, cuThread, expire);
                lockCacheMap.put(cuThread, newRedisLockInfo);
                // 开启锁续命线程
                new Thread(new LifeExtensionThread(newRedisLockInfo, 10000)).start();
                return true;
            }
            // 获取锁失败, 控制再次获取的超时时间
            Long endTime = System.currentTimeMillis();
            if (endTime - startTime > timeout) {
                log.info("<重试的时间已经过了,不能够在继续重试啦>");
                return false;
            }
        }

    }

锁实例

 public RedisLockInfo(String lockId, Thread lockThread, Long expire, Long reentry) {
        state = true;//锁状态, true-获取锁
        this.lockId = lockId; //锁的id
        this.lockThread = lockThread; //持有锁的线程
        this.expire = expire; //过期时间
        lifeCount = new AtomicInteger(0); // 续期次数, 原子操作
        this.reentry = reentry; //重试次数
    }

说明:

  1. 获取锁后, 记录锁实例信息, 并缓存到本地

  2. 控制获取锁的时间, 避免过去消耗cpu资源


释放锁

public boolean releaseLock() {
        //本地缓存查找当前线程锁实例
        RedisLockInfo redisLockInfo = lockCacheMap.get(Thread.currentThread());
        if (redisLockInfo == null) {
            return false;
        }
        String redisLockId = stringRedisTemplate.opsForValue().get(redisLockKey);
        if (StringUtils.isEmpty(redisLockId)) {
            log.error("该key已经过期或者不存在");
            return false;
        }
        //线程删除各自的锁
        String lockId = redisLockInfo.getLockId();
        if (!lockId.equals(redisLockId)) {
            log.error("不是当前我自己线程调用删除key");
            return false;
        }
        return stringRedisTemplate.delete(redisLockKey);
    }

说明:

在释放锁的时候, 会把当前线程lockid和redis中的redisLockId对比, 是为了防止误删

假设a线程业务时长20s, 锁时长10s, 那么在第10s的时候, 锁会被释放;

这时b线程获取锁, 当第20s时候, a线程执行完业务, 开始释放锁, 实际上释放的是b线程的锁

这时c线程获取锁...


锁续命

class LifeExtensionThread implements Runnable {
        private RedisLockInfo redisLockInfo;
        private int interval;

        public LifeExtensionThread(RedisLockInfo redisLockInfo, int interval) {
            this.redisLockInfo = redisLockInfo;
            this.interval = interval;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    // 每隔一段时间实现续命
                    Thread.sleep(interval);
                    // 锁实例为空, 不再续命
                    if (redisLockInfo == null) {
                        return;
                    }
                    //当前线程执行完毕, 锁已释放
                    Thread lockThread = redisLockInfo.getLockThread();
                    if (redisLockInfo.isState() && lockThread.isInterrupted()) {
                        log.info(">>当前获取到锁的线程,已经执行完毕啦, 锁已经释放啦。<<");
                        return;
                    }
                    //锁 对应线程还在一直使用 没有释放
                    Integer lifeCount = redisLockInfo.getLifeCount();
                    // 续命次数如果超过3次以上停止
                    if (lifeCount > 3) {
                        log.info(">>续命的次数已经达到了次数,开始主动释放锁<<");
                        // 1.回滚当前事务
                        //2.释放锁
                        stringRedisTemplate.delete(redisLockKey);
                        //3.该拿到锁的线程应该主动停止掉
                        lockThread.interrupt();
                        return;
                    }
                    // 延长过期key的时间, 结合lua脚本续命
                    stringRedisTemplate.expire(redisLockKey, redisLockInfo.getExpire(),TimeUnit.SECONDS); 
                } catch (Exception e) {
                    log.error(">>e:{e}<<", e);
                }
            }
        }
    }

说明:

  1. 开启一个线程死循环, 只要当前线程锁没有被释放, 就续命

  2. 可设置续命频率(默认10s), 续命时长(默认30s)和续命次数

  3. 锁一旦被释放, 就停止续命


http://www.kler.cn/news/9413.html

相关文章:

  • 软件测试今天你被内卷了吗?
  • 【LeetCode每日一题: 516. 最长回文子序列 | 暴力递归=>记忆化搜索=>动态规划 | 区间dp 】
  • 【华为OD机试】1035 - 判断两个IP是否属于同一子网
  • OpenText Content Server 客户案例——全球最大的商业炸药和创新爆破系统供应商Orica
  • 数据结构exp1_2学生成绩排序
  • MySQL库的操作
  • 博瑞智能云音箱云喇叭API开发定时播报文档(2023-4-5)
  • Linux进程间通信
  • 计讯物联小型水库雨水情测报与大坝安全监测一体化解决方案,确保水库安全运行
  • SpringAMQP
  • 数字化转型的避坑指南:细说数字化转型十二大坑
  • 实验6 TensorFlow基础
  • 亚马逊测评只能下单上好评?卖家倾向养号测评还有这些骚操作
  • Drone+Gitea CICD环境搭建流程笔记
  • Git(四):远程仓库的搭建、获取与更新
  • redis基础总结-常用命令
  • 初识C语言 ——“C Primer Plus”
  • TOGAF—架构治理
  • leaflet使用L.geoJSON加载文件,参数onEachFeature的使用方法(129)
  • git常用命令
  • 【前端】VUE3去掉控制台的warn信息
  • redis set list
  • 一天吃透计算机网络八股文
  • 初识设计模式 - 命令模式
  • 【ROS2指南-12】编写一个简单的发布者和订阅者
  • IDEA2020.1 Failed to execute goal org.codehaus.mojo:exec-maven-plugin
  • Sentry安装使用(最全最细)
  • 你是真的“C”——宏与函数的英雄本色
  • fetch下载js文件,js内容浏览器能不执行吗
  • 4月11日作业修订