redission分布式锁的使用

RLock lock = redissonClient.getLock("myLock");
lock.lock();
try {
System.out.println("aaa");
} catch (Exception e) {
System.out.println("bbb");
} finally {
lock.unlock();
}

获取锁的流程图

加锁代码流程(org.redisson.RedissonLock)

public void lock() {
try {
//参数意义 -1 代表不自动释放锁,null时间单位,false加锁期间线程被中断将抛出异常
lock(-1, null, false);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}
  /**
* 加锁
* @param leaseTime 锁的使用时间,超过该时间,锁便自动释放
* @param unit 时间单位
* @param interruptibly 加锁期间是否可以被中断
* @return void
* @author liekkas 2021-02-27 15:46
*/
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
//获取当前线程的Id
long threadId = Thread.currentThread().getId();
//加锁,如果ttl为null,则说明ttl(time to live)为空,加锁成功,否则加锁不成功。
Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
//加锁成功,直接返回
if (ttl == null) {
return;
}
//加锁失败,订阅该线程,释放锁的时候会发布一个消息,锁没有释放的时候则会等待,直到锁释放的时候会执行下面的while循环,重新竞争锁。此处是用了异步的模式。
RFuture<RedissonLockEntry> future = subscribe(threadId);
if (interruptibly) {
commandExecutor.syncSubscriptionInterrupted(future);
} else {
commandExecutor.syncSubscription(future);
} try {
while (true) {
//锁被释放,重新竞争锁
ttl = tryAcquire(-1, leaseTime, unit, threadId);
// 竞争获取锁成功,退出循环,不再竞争。
if (ttl == null) {
break;
} // 竞争获取锁失败,则排队等待所释放,重新竞争锁。
if (ttl >= 0) {
try {
//利用信号量机制阻塞当前线程ttl时间,之后再重新获取锁,如果当前线程被中断,则抛出 InterruptedException异常
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();
}
}
}
} finally {
//竞争锁成功后,取消订阅该线程Id事件
unsubscribe(future, threadId);
}
}
/**
* 利用future模式异步返回锁的剩余生存时间
* @param waitTime 等待时间
* @param leaseTime 锁的使用时间,超过改时间,锁便自动释放
* @param unit 时间单位
* @param threadId 当前线程Id
* @return 锁的剩余生存时间
* @author liekkas 2021-02-27 15:46
*/
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
/**
*
* @param waitTime 等待时间
* @param leaseTime 锁的使用时间,超过改时间,锁便自动释放
* @param unit 时间单位
* @param threadId 当前线程Id
* @return 锁的剩余生存时间
* @author liekkas 2021-02-27 15:46
*/
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
//如果锁的租用时间不为-1,则直接获取锁,到时自动释放锁。
if (leaseTime != -1) {
return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
//如果锁的租用时间为-1,则利用默认的租用时间30s,获取锁
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(waitTime,
commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
} // 获取锁成功,启用watchDog,自动为锁延长寿命,保证该锁被其持有者释放。
if (ttlRemaining == null) {
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}

lua脚本加锁流程图

/**
* 使用lua脚本加锁
* @param waitTime 等待时间
* @param leaseTime 锁的使用时间,超过改时间,锁便自动释放
* @param unit 时间单位
* @param threadId 当前线程Id
* @param command 执行lua脚本命令
* @return 锁的剩余生存时间
* @author liekkas 2021-02-27 15:46
*/
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
/**
* nil相当于java的null,后面描述为了方便就用null代替nil
* KEYS[1]:锁的名称,ARGV[1]:锁的过期时间,ARGV[2]:线程Id标识名称
* lua脚本的逻辑:
* 首先判断KEYS[1]是否存在,
* 如果不存在,则利用hincrby命令把KEYS[1]的ARGV[2]的值加一,并且设置KEYS[1]的
* 的过期时间为ARGV[1],返回null.
* 如果KEYS[1]存在,则利用hexists判断KEYS[1]的ARGV[2]是否存在,
* 如果存在,则利用hincrby命令把KEYS[1]的ARGV[2]的值加一,并且设置KEYS[1]的
* 的过期时间为ARGV[1],返回KEYS[1]的存活时间。
*/
return evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

锁的释放流程

lua脚本释放锁的流程图

@Override
public void unlock() {
try {
get(unlockAsync(Thread.currentThread().getId()));
} catch (RedisException e) {
if (e.getCause() instanceof IllegalMonitorStateException) {
throw (IllegalMonitorStateException) e.getCause();
} else {
throw e;
}
}
public RFuture<Void> unlockAsync(long threadId) {
RPromise<Void> result = new RedissonPromise<Void>();
//释放锁
RFuture<Boolean> future = unlockInnerAsync(threadId); future.onComplete((opStatus, e) -> {
//取消锁的延续时间
cancelExpirationRenewal(threadId); if (e != null) {
result.tryFailure(e);
return;
} if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + threadId);
result.tryFailure(cause);
return;
} result.trySuccess(null);
}); return result;
}
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
/**
* nil相当于java的null,后面描述为了方便就用null代替nil
* KEYS[1]:锁的名称,KEYS[2]:发布订阅消息的管道名称,
* ARGV[1]:发布的消息内容,ARGV[2]:锁的过期时间,ARGV[3]:线程Id标识名称
* lua脚本的逻辑:
* 首先判断KEYS[1]的ARGV[3]是否存在,
* 如果不存在,直接返回null,确保锁只能被持有的释放.
* 如果存在,则KEYS[1]的ARGV[3]的值减一,
* 如果ARGV[3]仍然大于0,由于是可重入锁,则说明该线程仍然持有该锁,
* 重新设置过期时间,返回0结束。
* 如果ARGV[3]小于等于于0,则删除KEYS[1],并发布KEYS[2],ARGV[1]消息,该锁已释放。返回1结束。
* 如果存在,则利用hincrby命令把KEYS[1]的ARGV[2]的值加一,并且设置KEYS[1]的
* 的过期时间为ARGV[1],返回KEYS[1]的存活时间。
*/
return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end; " +
"return nil;",
Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}

总结

以上就是redission的加锁和释放锁的流程,从阅读源码的过程中也学到了很多设计思想,对以后的编码大有裨益!

Redission加锁解锁流程的更多相关文章

  1. 多线程与高并发(二)—— Synchronized 加锁解锁流程

    前言 上篇主要对 Synchronized 的锁实现原理 Monitor 机制进行了介绍,由于 Monitor 基于操作系统调用,上下文切换导致开销大,在竞争不激烈时性能不算很好, 在 jdk6 之后 ...

  2. Linux 进程与线程四(加锁--解锁)

    线程共享进程的内存空间,打开的文件描述符,全局变量. 当有多个线程同事访问一块内存空间或者一个变量.一个文件描述符,如果不加控制,那么可能会出现意想不到的结果. 原子操作 对于我们的高级语言(C语言, ...

  3. 使用redis的比较完美的加锁解锁

    使用redis的比较完美的加锁解锁 tags:redis read&write redis加锁和解锁 php 习惯性说一下写这篇文章要说明什么,我们经常用redis进行加锁操作,目的是为了解决 ...

  4. 进程间通信(IPC)+进程加锁解锁

    [0]README 0.1) source code and text description are from orange's implemention of a os: 0.2) for com ...

  5. 从ReentrantLock加锁解锁角度分析AQS

    本文用于记录在学习AQS时,以ReentrantLock为切入点,深入源码分析ReentrantLock的加锁和解锁过程. 同步器AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理 ...

  6. chattr -lsattr 文件加锁解锁简单用法

    chattr: 加锁文件,无修改,无删除权限. 常用参数:        +a:  可给文件追加内容,但无法删除. +i  加锁文件(文件不能被删除.改名.设定链接关系,同时不能写入或追加内容) -i ...

  7. 加锁解锁PHP实现 -转载

    PHP并没有完善的线程支持,甚至部署到基于线程模型的httpd服务器都会产生一些问题,但即使是多进程模型下的PHP,也难免出现多进程共同访问同一资源的情况. 比如整个程序共享的数据缓存,或者因为资源受 ...

  8. 分析NonfairSync加锁/解锁过程

    类继承关系: NonfairSync => Sync => AbstractQueuedSynchronizer 类NonfairSync final void lock() { if ( ...

  9. Oracle EBS 加锁解锁程序

    FUNCTION request_lock(p_lock_name IN VARCHAR2) RETURN BOOLEAN IS l_lock_name ); l_lock_ret INTEGER; ...

随机推荐

  1. 一文带你全面了解java对象的序列化和反序列化

    摘要:这篇文章主要给大家介绍了关于java中对象的序列化与反序列化的相关内容,文中通过详细示例代码介绍,希望能对大家有所帮助. 本文分享自华为云社区<java中什么是序列化和反序列化?>, ...

  2. 使用TK框架中 insert与insertSelective区别

    insertSelective会对字段进行判断再更新(如果为Null就忽略更新),如果你只想插入某些字段,可以用这个方法. insert对你注入的字段全部插入

  3. 想要测试Dubbo接口?测试的关键点在哪里?

    Dubbo接口如何测试? 这个dubbo如何测试,dubbo接口测试什么玩意儿?   RPC的有一个类型,叫Dubbo接口. 那这个接口如何测试?测试的关键点在哪里? 这个面试问题,我觉得大家可能就有 ...

  4. 11.qml-通过方法来加载组件、字符串方式加载组件

    在上章,我们学习了10.qml-组件.Loader.Component介绍. 本章我们继续来学习组件的其它创建方式. 1.调用Function来加载和移除组件 之前我们是使用Loader对象来实现加载 ...

  5. 下载最新版本Fiddler

    下载最新版本Fiddler https://www.telerik.com/download/fiddler/fiddler-everywhere-windows

  6. /etc/ssh/sshd_config ssh自动断 cent7

    vim /etc/ssh/sshd_config ClientAliveInterval 60ClientAliveCountMax 8630000 ClientAliveInterval 30Cli ...

  7. C语言中位运算异或“∧”的作用

    1.概念异或运算符"∧"也称XOR运算符.它的规则是若参加运算的两个二进位同号,则结果为0(假):异号则为1(真).即 0∧0=0,0∧1=1, 1^0=1,1∧1=0.运算    ...

  8. linux进程间通信-(转自 临水)

    一.进程间通信概述进程通信有如下一些目的:A.数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间B.共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别 ...

  9. 基于 IntersectionObserver 实现一个组件的曝光监控

    我们在产品推广过程中,经常需要判断用户是否对某个模块感兴趣.那么就需要获取该模块的曝光量和用户对该模块的点击量,若点击量/曝光量越高,说明该模块越有吸引力. 那么如何知道模块对用户是否曝光了呢?之前我 ...

  10. 从Lombok到JSR-269

    前言 Lombok的出现帮助开发人员在开发工程中消除了大部分冗余代码:繁琐的get.set方法甚至建造者模式. Lombok的实现方式是什么呢? 新建一个测试类使用Lombok的Getter和Sett ...