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

获取锁


Long timeout = 3000L;//获取锁超时时间
Map 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. 锁一旦被释放, 就停止续命

你可能感兴趣的:(java,开发语言)