Redisson实现分布式锁以及lock()方法源码,流程解析

  1. 创建Redisson
    这里创建了一个单体的Redisson,集群创建方式参考redisson官网
    @Value("${spring.redis.host}")
    String host;
    @Value("${spring.redis.port}")
    String port;
    @Value("${spring.redis.password}")
    String password;

    @Bean
    public RedissonClient redisson() {
        Config config = new Config();
        SingleServerConfig serverConfig = config.useSingleServer().setAddress("redis://" + host + ":" + port);
        if (StringUtils.isNotBlank(password)) {
            serverConfig.setPassword(password);
        }
        config.useSingleServer().setConnectionMinimumIdleSize(5);
        RedissonClient client = Redisson.create(config);
        return client;
    }
  1. 获取并创建锁
    使用@Resource注解引入RedissonClient
    @Resource
    RedissonClient redissonClient;

然后调用redissonClient的getLock方法,传入锁的名称,名称任意编写,没有特殊要求

      RLock lock = redissonClient.getLock("lock");
      lock.lock();

记得使用完了之后unlock()一下

lock.unlock();

这里就获取并创建了一个分布式锁。
分布式锁原理:
redisson是基于了redis做的一个分布式锁,使用了类似redis的set key value nx命令的脚本,做的一个原子性建锁操作,而set key value ExpirationTime NX,重点在于它的NX,这个关键字的意思就是,如果锁不存在,则设置锁,并返回1(Long类型),如果锁存在,这返回0,锁存在,就代表着,有线程获取到了锁,并正在执行任务,其他的线程,会进入阻塞状态,在外部等待。

而redisson有一个特点,就是在我们不设置过期时间时,会自动设置一个默认的30s过期时间 this.lockWatchdogTimeout = 30000L;
并且,如果线程执行的任务时间,超过了 30000L / 3L ,看门狗就会将锁的超时时间,自动重新续期到30s,直到任务执行结束。
而任务如果在执行途中,程序死掉了,没有主动触发unlock()方法,也不会造成死锁,看门狗在线程结束的之后,监测到锁没有被释放,就会在30s之后自动释放锁,解决了死锁问题,

lock()无参方法源码解析

	  public void lock() {
        try {
            this.lock(-1L, (TimeUnit)null, false);
        } catch (InterruptedException var2) {
            throw new IllegalStateException();
        }
    }

进入lock方法之后,会调用内部的重构的lock方法,三个参数为,等待时间(-1为永久),超时时间(注意这里传入了一个null),是否可中断
是否可中断的意思是,如果有线程获取到了锁,则其他的线程不进入阻塞状态,直接获取锁失败,比如A线程获取到了锁,这是B线程也来获取锁,会进入阻塞状态,而lock的interruptibly参数设置为了true,B线程就会调用syncSubscriptionInterrupted()中断阻塞状态。

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
		//获取到当前线程id
        long threadId = Thread.currentThread().getId();
        //尝试获取锁(看下一个方法详解)
        Long ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
        if (ttl != null) {
            RFuture<RedissonLockEntry> future = this.subscribe(threadId);
            if (interruptibly) {
                this.commandExecutor.syncSubscriptionInterrupted(future);
            } else {
                this.commandExecutor.syncSubscription(future);
            }

            try {
	            //自旋重试获取锁
                while(true) {
                    ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
                    if (ttl == null) {
                        return;
                    }

                    if (ttl >= 0L) {
                        try {
                            ((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                        } catch (InterruptedException var13) {
                            if (interruptibly) {
                                throw var13;
                            }

                            ((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                        }
                    } else if (interruptibly) {
                        ((RedissonLockEntry)future.getNow()).getLatch().acquire();
                    } else {
                        ((RedissonLockEntry)future.getNow()).getLatch().acquireUninterruptibly();
                    }
                }
            } finally {
                this.unsubscribe(future, threadId);
            }
        }
    }

tryAcquire方法解析

  private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
	    //调用异步获取锁方法,tryAcquireAsync(此方法是真正的获取锁的方法)
        return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
    }

tryAcquireAsync方法解析

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture ttlRemainingFuture;
        //如果laseTime不为-1,则表示我们自定义了过期时间,那么锁将不会自动延期,在设置锁时,就会把过期时间设置上,到期redis自动删除
        if (leaseTime != -1L) {
            ttlRemainingFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
	        //否则,过期时间传入默认的,this.internalLockLeaseTime,这个参数是公共属性,在构造RedissonLock对象时进行设置,参考下图
            ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        }
		//获取锁成功
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        //如果e==null,则表示获取锁的过程中没有出现bug
            if (e == null) {
            //ttlRemaining ==null则表示没有锁,可以进行设置
                if (ttlRemaining == null) {
                	//leaseTime  != -1L就表示用户自己设置了过期时间,不需要进行看门狗调度延期,到期自动删除
                    if (leaseTime != -1L) {
                        this.internalLockLeaseTime = unit.toMillis(leaseTime);
                    } else {
	                    //如果用户没有设置过期时间,则进行调度延期(看门狗实现锁超时时间自动延长)
	                    //调度方法详解,看下面**scheduleExpirationRenewal详解**
                        this.scheduleExpirationRenewal(threadId);
                    }
                }

            }
        });
        return ttlRemainingFuture;
    }

scheduleExpirationRenewal详解

	//调度延期
    protected void scheduleExpirationRenewal(long threadId) {
    	//获取要延期的锁信息集合Map
        RedissonBaseLock.ExpirationEntry entry = new RedissonBaseLock.ExpirationEntry();
        //this就是调用scheduleExpirationRenewal的RedissonLock对象,通过锁名称,获取到要延期的锁信息
        //第一次获取肯定是空的,则会进入else
        //重点在于renewExpiration()方法,这个方法才是延期的方法
        RedissonBaseLock.ExpirationEntry oldEntry = (RedissonBaseLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            entry.addThreadId(threadId);
            this.renewExpiration();
        }

    }

renewExpiration()方法详解

 private void renewExpiration() {
		 //拿到需要延期的锁信息
        RedissonBaseLock.ExpirationEntry ee = (RedissonBaseLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
        //如果锁信息不为空
        if (ee != null) {
	        //构建一个定时任务,周期为 this.internalLockLeaseTime / 3L,而this.internalLockLeaseTime就是上面提到的默认超时时间30000L,也就是30s,30000L / 3L,得出周期时间为,10秒,也就是说,10秒检查一次,每次都会将锁的过期时间延长至30秒
            Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                public void run(Timeout timeout) throws Exception {
                //获取要延期的锁信息
                    RedissonBaseLock.ExpirationEntry ent = (RedissonBaseLock.ExpirationEntry)RedissonBaseLock.EXPIRATION_RENEWAL_MAP.get(RedissonBaseLock.this.getEntryName());
                    //锁存在才继续,否则退出
                    if (ent != null) {
                        Long threadId = ent.getFirstThreadId();
                        //线程id不为空才继续,否则退出
                        if (threadId != null) {
                        //通过lua脚本进行延期
                            RFuture<Boolean> future = RedissonBaseLock.this.renewExpirationAsync(threadId);
                            future.onComplete((res, e) -> {
                            	//如果e不为空,代表有异常,从待延期锁信息集合中删除当前锁,并退出
                                if (e != null) {
                                    RedissonBaseLock.log.error("Can't update lock " + RedissonBaseLock.this.getRawName() + " expiration", e);
                                    RedissonBaseLock.EXPIRATION_RENEWAL_MAP.remove(RedissonBaseLock.this.getEntryName());
                                } else {
                                	//延期成功
	                                //递归调度,进入下一次延期
                                    if (res) {
                                        RedissonBaseLock.this.renewExpiration();
                                    }

                                }
                            });
                        }
                    }
                }
            }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
            ee.setTimeout(task);
        }
    }

设置默认过期时间
最终是在Config类中被设置的
在这里插入图片描述
30000微秒,也就是30秒

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值