简介

关于 Redisson 的具体介绍可点击 这里,简单来说就是将 JUC 和 Redis 结合起来,使其可以实现多机器多线程同步的功能,Redisson 有很多组件,这篇主要介绍可重入锁 —— ReentantLock。

环境准备

添加 Maven 依赖

<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>

添加配置类

@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.56.10:6379");
return Redisson.create(config);
}
}

基本使用代码如下:

@GetMapping("/hello")
@ResponseBody
public String hello() {
//获取Lock锁,设置锁的名称
RLock lock = redisson.getLock("my-lock");
//开启
lock.lock();
try {
System.out.println("上锁:" + Thread.currentThread().getId());
//模拟业务处理20秒
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("解锁:" + Thread.currentThread().getId());
//释放
lock.unlock();
}
return "hello";
}

分析

当我们发送 /hello 请求后等待 20 秒得到响应结果,会在 Redis 中存储锁的信息(如下图所示),期间,其它用户发送 /hello 请求时会被阻塞,只有前一个请求结束后释放锁,当前请求才会进入。

思考1:如果在业务处理过程中程序突然终止,锁没有得到释放,是否会一直阻塞下去?

经过实验,在业务处理的20秒中,将服务手动停止,刷新 Redis 中 my-lock 的信息,发现 TTL 不断的减小,直到失效,再发送其它请求能够正常执行,这说明,即使不释放锁,锁的有效时间到了也会自动释放。源码如下:

//获取当前线程id
long threadId = Thread.currentThread().getId();
//获取此线程的锁
Long ttl = tryAcquire(leaseTime, unit, threadId);
//如果获取不到,则说明锁已经释放了,直接返回
if (ttl == null) {
return;
}
while (true) {
ttl = tryAcquire(leaseTime, unit, threadId);
//和上面一样,判断是否能获取到锁
if (ttl == null) {
break;
}
...
}

思考2:过期时间是多少?如果我们的业务处理时间超过了过期时间,岂不是还没处理完就把锁的信息给删了?

正常启动服务访问 /hello,刷新 my-lock 的信息,我们发现,TTL 每次减少到 20 就再次变为 30,直到业务处理完成,my-lock 被删除。查找相关源代码如下:

while (true) {
//尝试获取锁
ttl = tryAcquire(leaseTime, unit, threadId);
//如果获取不到,说明执行该线程执行结束,就终止循环
if (ttl == null) {
break;
} //如果获取到了就继续循环
if (ttl >= 0) {
try {
future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (interruptibly) {
throw e;
}
future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else {
if (interruptibly) {
future.getNow().getLatch().acquire();
} else {
future.getNow().getLatch().acquireUninterruptibly();
}
}
}

继续深入源码可以看到,如果不指定锁的时间,就默认为 30 秒,它有一个好听的名字:看门狗

private long lockWatchdogTimeout = 30 * 1000;

只要占领锁,就会启动一个定时任务:每隔一段时间重新给锁设置过期时间

protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"end; " +
"return 0;",
Collections.<Object>singletonList(getName()),
internalLockLeaseTime, getLockName(threadId));
//internalLockLeaseTime就是看门狗的时间
}

每隔多长久刷新一下呢?

//获取看门狗的时间,赋值给自己
this.internalLockLeaseTime = xxx.getLockWatchdogTimeout();
public long getLockWatchdogTimeout() {
return lockWatchdogTimeout;
} Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
...
}
//使用的时候除3,也就是10秒刷新一次
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

思考三:如何自定义过期时间?

lock() 方法还有一个重载方法,可以传入过期时间和单位

void lock(long leaseTime, TimeUnit unit);

我们将之前的代码修改,设置为 15 秒,重启服务再测试

lock.lock(15, TimeUnit.SECONDS);

访问 /hello,刷新 Redis 中 my-lock 的信息会发现,TTL 从 15 减到 0,然后锁信息过期,并不会出现之前的 10秒一刷新,查看源码:

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
//如果传入了过期时间,则直接执行tryLockInnerAsync里面的Lua脚本
if (leaseTime != -1) {
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
//没有传入过期时间,执行下面的逻辑
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
//有异常,直接返回
if (e != null) {
return;
}
if (ttlRemaining == null) {
//刷新过期时间
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}

总结

1、Reentrant Lock 对其它线程是阻塞的

2、为了解决死锁的问题,Redisson 内部提供了一个监控锁的看门狗,只要 Redisson 实例没被关闭就不断延长锁的有效时间,默认情况下,看门狗的检查锁的超时时间是 30 秒,检查时间是 10 秒(超时时间的三分之一),可以通过 setLockWatchdogTimeout 设置(只适用于未指定锁的时间的情况)

3、如果指定锁的时间,到达指定时间会自动解锁,因此设置的时间必须大于业务正常执行的时间,否则,业务没执行完,锁就会被释放

4、推荐使用指定时间的方式,省掉了续期操作,但需要合理设置过期时间,不能使锁过早释放

分布式锁-Redission-Lock锁的使用与原理的更多相关文章

  1. Java核心知识点学习----线程中如何创建锁和使用锁 Lock,设计一个缓存系统

    理论知识很枯燥,但这些都是基本功,学完可能会忘,但等用的时候,会发觉之前的学习是非常有意义的,学习线程就是这样子的. 1.如何创建锁? Lock lock = new ReentrantLock(); ...

  2. Java核心知识点 --- 线程中如何创建锁和使用锁 Lock , 设计一个缓存系统

    理论知识很枯燥,但这些都是基本功,学完可能会忘,但等用的时候,会发觉之前的学习是非常有意义的,学习线程就是这样子的. 1.如何创建锁? Lock lock = new ReentrantLock(); ...

  3. 关于lock锁

    在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁. lo ...

  4. Lock锁 精讲

    1.为什么需要Lock 为什么synchronized不够用,还需要Lock Lock和synchronized这两个最常见的锁都可以达到线程安全的目的,但是功能上有很大不同. Lock并不是用来代替 ...

  5. 分布式锁Redission

    Redisson 作为分布式锁 官方文档:https://github.com/redisson/redisson/wiki 引入依赖 <dependency> <groupId&g ...

  6. 从构建分布式秒杀系统聊聊Lock锁使用中的坑

    前言 在单体架构的秒杀活动中,为了减轻DB层的压力,这里我们采用了Lock锁来实现秒杀用户排队抢购.然而很不幸的是尽管使用了锁,但是测试过程中仍然会超卖,执行了N多次发现依然有问题.输出一下代码吧,可 ...

  7. Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析

    原文:Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析 一.RedissonLock#lock 源码分析 1.根据锁key计算出 slot,一个slot对 ...

  8. 【分布式锁】03-使用Redisson实现RedLock原理

    前言 前面已经学习了Redission可重入锁以及公平锁的原理,接着看看Redission是如何来实现RedLock的. RedLock原理 RedLock是基于redis实现的分布式锁,它能够保证以 ...

  9. redis实现分布式可重入锁

    利用redis可以实现分布式锁,demo如下: /** * 保存每个线程独有的token */ private static ThreadLocal<String> tokenMap = ...

  10. 多线程系列之自己实现一个 lock 锁

    我们面试中经常会被问到多线程相关知识,这一块内容往浅了说大家都会,但是一问到底层实现原理,我们往往就一脸懵逼. 这段时间准备好好学习多线程,接下来会写一系列关于多线程的知识. 我们首先要了解线程,百度 ...

随机推荐

  1. Chrome 浏览器安装 ChroPath 插件

    1.下载地址 http://www.cnplugins.com/devtool/chropath/download.html 2.安装方法 a.把下载的文件更改后缀名变为压缩包,然后解压到本地:如下图 ...

  2. ASP.Net内置对象之网页之间传参(二)

    Session对象 运用于多个界面调用某一个特定的用户信息,也就是每个Session 对象是独立的,个不受影响. Session对象的读取和存储 Session[name]=”chen”; 可以用来界 ...

  3. PHP 把MYSQL重复ID 二维数组重组为三维数组

    应用场景 MYSQL在使用关联查询时,比如 产品表 与 产品图片表关联,一个产品多张产品图片,关联查询结果如下: $arr=[['id'=>1,'img'=>'img1'],['id'=& ...

  4. Apache Rewrite实现URL的跳转和域名跳转

    Apache Rewrite实现URL的跳转和域名跳转   Rewirte主要的功能就是实现URL的跳转,它的正则表达式是基于Perl语言.可基 于服务器级的(httpd.conf)和目录级的 (.h ...

  5. Openstack object list 一次最多有一万个 object

    When you request a list of containers or objects, Object Storage returns a maximum of 10,000 names f ...

  6. 通过transmittable-thread-local源码理解线程池线程本地变量传递的原理

    前提 最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文.想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能 ...

  7. Android Studio SVN配置忽略文件

    1.用Android Studio创建一个项目,会在根目录和Module目录下自动生成.gitignore文件,貌似是Git的配置文件,和SVN没有关系. 2.打开Setting-Version Co ...

  8. 前线观察 | AWS re:Invent 2018见闻实录

    作为云计算行业科技盛会,AWS:reInvent大会近年来越来越受关注,其中尤其被关注的分别是CEO Andy Jassy和CTO Werner Vogels的Keynote演讲.2018年11月28 ...

  9. Spring.getBean()流程和循环依赖的解决

    getBean流程介绍(以单例的Bean流程为准) getBean(beanName) 从BeanFactory中获取Bean的实例对象,真正获取的逻辑由doGetBean实现. doGetBean( ...

  10. CF思维联系–CodeForces -224C - Bracket Sequence

    ACM思维题训练集合 A bracket sequence is a string, containing only characters "(", ")", ...