redis 读写锁实现
一
先搞清楚读写锁要做什么。
基本就是
读读不互斥,读写互斥,写写互斥。可重入。
关于redis读写锁,我写了一次之后,总觉得很怪,然后就上网看到大神的redisson了,果断借鉴一番。
二
读行为
当写锁未获取,加上读锁(通知其他请求数据在读状态),读数据
当写锁被获取,等待,直到写锁未获取,加读锁,读数据
写行为
当写锁未获取,等待获取写锁
当写锁被获取,加写锁。读锁未获取,等待获取读锁
当写锁被获取,读锁被获取,写数据
可以看出读锁可重入一定意义都没有,写锁才有意义
三 初版
先说下总结
1.重入也只是本机重入,不能实现锁在其他服务器的重入。
2.读写锁获取锁的时候,是两个redis操作,原子性不行,所以要用redis的eval命令或者直接使用lua脚本。
3.用switch来判断读写模式太蠢了,代码可读性低,早期想的简单,但是逻辑一复杂就很麻烦了。
ps.
spring自带的redisTemplate则没有提供eval的接口,只提供使用lua脚本,相应的读写锁代码要自己写。
netty自带的redisson则是用了eval命令,则已经写好了代码,只需要傻瓜式调用就好了。
代码
- --存放读写锁的信息
public enum LockModel {- READ("%s:READ"),
- WRITE("%s:WRITE"),;
- String lockFormat;
- LockModel(String lockFormat) {
- this.lockFormat = lockFormat;
- }
- public String getLockModelName() {
- return super.name();
- }
- public String getLockFormat() {
- return lockFormat;
- }
- public static void main(String[] args) {
- LockModel read = LockModel.READ;
- System.out.println(read.getLockFormat());
- System.out.println(read.getLockModelName());
- }
- }
- --实现java自带的读写锁接口
public class ReadWriteLock implements java.util.concurrent.locks.ReadWriteLock {
/**
* 应该是唯一标识组成的key,可以使线程id,可以使用户id,可以使服务器id
*/
String name;
/**
* 毫秒
* */
Long timeInterval;- public ReadWriteLock(String name, Long timeInterval) {
this.name = name;
this.timeInterval = timeInterval;
}- @Override
public Lock readLock() {
return new ReentrantLock(this, LockModel.READ);
}- @Override
public Lock writeLock() {
return new ReentrantLock(this, LockModel.WRITE);
}- }
- --重入锁
public class ReentrantLock implements Lock {- @Autowired
RedisTemplate redisTemplate;- ReadWriteLock rwLock;
LockModel lockModel;
String lockName;
Long deadTime = 0L;
boolean localWriteLocked = false;- public ReentrantLock(ReadWriteLock rwLock, LockModel lockModel) {
this.rwLock = rwLock;
this.lockModel = lockModel;
setLockName(lockModel);
}- @Override
public void lock() {
try {
lockInterruptibly();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}- @Override
public void lockInterruptibly() throws InterruptedException {
switch (getLockModel()) {
case WRITE:
if (!isLocalWriteLocked()) {
setLockModel(LockModel.READ);
while (!tryLock()) {
Thread.sleep(500);
}
redisTemplate.opsForValue().set(getLockName(), getDeadTime(), getRwLock().getTimeInterval());- setLockModel(LockModel.WRITE);
while (!tryLock()) {
Thread.sleep(500);
}
setLocalWriteLocked(true);
} else {
/**
* 本机持有写锁,重入,但要等待之前的写操作完成
* */
while (!isLocalWriteLocked()) {
Thread.sleep(500);
}- /**
* 更新写锁的过期时间
* */
redisTemplate.opsForValue().set(getLockName(), getDeadTime(), getRwLock().getTimeInterval());
setLocalWriteLocked(true);
}
break;
case READ:
while (!tryLock()) {
Thread.sleep(500);
}
setDeadTime();
redisTemplate.opsForValue().set(getLockName(), getDeadTime(), getRwLock().getTimeInterval());
break;
}
}- @Override
public boolean tryLock() {
return null != redisTemplate.opsForValue().get(getOpposeLockName());
}- @Override
public void unlock() {
switch (getLockModel()) {
case WRITE:
if (isLocalWriteLocked()) {
setLocalWriteLocked(false);
}
redisTemplate.delete(getLockName());
break;
case READ:
redisTemplate.delete(getLockName());
break;
}
}- public Long getTimeInterval() {
return rwLock.getTimeInterval();
}- public void setDeadTime() {
this.deadTime = System.currentTimeMillis() + getTimeInterval();
}- private String getOpposeLockName() {
String opposeLockName = "";
switch (getLockModel()) {
case READ:
opposeLockName = String.format(LockModel.WRITE.getLockFormat(), getRwLock().getName());
break;
case WRITE:
opposeLockName = String.format(LockModel.READ.getLockFormat(), getRwLock().getName());
break;
default:
break;
}
return opposeLockName;
}
- }
四 redisson分析
还是先总结
1.用hashmap存读写锁的信息。读锁写锁的本质则是model的不同。读锁写锁只是不同的mapfield。而读锁还有过期时间为属性。
2.用频道记录线程的操作。具体为什么用频道就要看LockPubSub和PublishSubscribe,这里因为不涉及我就不细说了。
RedissonReadLock
- // 判断有没有锁
@Override- public boolean isLocked() {
- RFuture<String> future = commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.HGET, getName(), "mode");
- String res = get(future);
- return "read".equals(res);
- }
可以看出尝试获取锁的状态的代码都写的很简单,但是redisson用了hashmap来存放。
- @Override
- <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
- internalLockLeaseTime = unit.toMillis(leaseTime);
- return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
- "local mode = redis.call('hget', KEYS[1], 'mode'); " +
//锁出错- "if (mode == false) then " +
- "redis.call('hset', KEYS[1], 'mode', 'read'); " +
- "redis.call('hset', KEYS[1], ARGV[2], 1); " +
- "redis.call('set', KEYS[2] .. ':1', 1); " +
- "redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " +
- "redis.call('pexpire', KEYS[1], ARGV[1]); " +
- "return nil; " +
- "end; " +
//在读模式或者本线程获取写锁的时候进行读- "if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
- "local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
- "local key = KEYS[2] .. ':' .. ind;" +
- "redis.call('set', key, 1); " +
- "redis.call('pexpire', key, ARGV[1]); " +
- "redis.call('pexpire', KEYS[1], ARGV[1]); " +
- "return nil; " +
- "end;" +
- "return redis.call('pttl', KEYS[1]);",
- Arrays.<Object>asList(getName(), getReadWriteTimeoutNamePrefix(threadId)),
- internalLockLeaseTime, getLockName(threadId), getWriteLockName(threadId));
- }
注意里面的getWriteLockName(threadId)
- protected RFuture<Boolean> unlockInnerAsync(long threadId) {
- String timeoutPrefix = getReadWriteTimeoutNamePrefix(threadId);
- String keyPrefix = getKeyPrefix(threadId, timeoutPrefix);
- return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
- "local mode = redis.call('hget', KEYS[1], 'mode'); " +
- "if (mode == false) then " +
- "redis.call('publish', KEYS[2], ARGV[1]); " +
- "return 1; " +
- "end; " +
//锁不存在- "local lockExists = redis.call('hexists', KEYS[1], ARGV[2]); " +
- "if (lockExists == 0) then " +
- "return nil;" +
- "end; " +
- //给读锁的值-1,返回结果值
- "local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " +
// 结果值为0,删除读锁- "if (counter == 0) then " +
- "redis.call('hdel', KEYS[1], ARGV[2]); " +
- "end;" +
// 把自己的超时标记删除- "redis.call('del', KEYS[3] .. ':' .. (counter+1)); " +
- //还有其他读
- "if (redis.call('hlen', KEYS[1]) > 1) then " +
- "local maxRemainTime = -3; " +
- "local keys = redis.call('hkeys', KEYS[1]); " +
- "for n, key in ipairs(keys) do " +
- "counter = tonumber(redis.call('hget', KEYS[1], key)); " +
- "if type(counter) == 'number' then " +
- "for i=counter, 1, -1 do " +
- "local remainTime = redis.call('pttl', KEYS[4] .. ':' .. key .. ':rwlock_timeout:' .. i); " +
- "maxRemainTime = math.max(remainTime, maxRemainTime);" +
- "end; " +
- "end; " +
- "end; " +
- "if maxRemainTime > 0 then " +
- "redis.call('pexpire', KEYS[1], maxRemainTime); " +
- "return 0; " +
- "end;" +
- //有写锁直接返回
- "if mode == 'write' then " +
- "return 0;" +
- "end; " +
- "end; " +
- "redis.call('del', KEYS[1]); " +
- "redis.call('publish', KEYS[2], ARGV[1]); " +
- "return 1; ",
- Arrays.<Object>asList(getName(), getChannelName(), timeoutPrefix, keyPrefix),
- LockPubSub.unlockMessage, getLockName(threadId));
- }
解锁还给其他锁续命,,,最大存活时间maxRemainTime很有意思,存在就给他加上等量的剩余存活时间,而不是固定加多少。那是不是无限续然后过期不了?但是这里是读写锁的存活时间而不是读锁的时间。
并且publish到相应的频道,更新状态。
- protected RFuture<Boolean> renewExpirationAsync(long threadId) {
- String timeoutPrefix = getReadWriteTimeoutNamePrefix(threadId);
- String keyPrefix = getKeyPrefix(threadId, timeoutPrefix);
- return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
- "local counter = redis.call('hget', KEYS[1], ARGV[2]); " +
//不是false- "if (counter ~= false) then " +
- "redis.call('pexpire', KEYS[1], ARGV[1]); " +
- "if (redis.call('hlen', KEYS[1]) > 1) then " +
- "local keys = redis.call('hkeys', KEYS[1]); " +
- "for n, key in ipairs(keys) do " +
- "counter = tonumber(redis.call('hget', KEYS[1], key)); " +
- "if type(counter) == 'number' then " +
- "for i=counter, 1, -1 do " +
- "redis.call('pexpire', KEYS[2] .. ':' .. key .. ':rwlock_timeout:' .. i, ARGV[1]); " +
- "end; " +
- "end; " +
- "end; " +
- "end; " +
- "return 1; " +
- "end; " +
- "return 0;",
- Arrays.<Object>asList(getName(), keyPrefix),
- internalLockLeaseTime, getLockName(threadId));
- }
刷新存活时间没啥特殊的
- @Override
- public RFuture<Boolean> forceUnlockAsync() {
- cancelExpirationRenewal(null);
- return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
- "if (redis.call('hget', KEYS[1], 'mode') == 'read') then " +
- "redis.call('del', KEYS[1]); " +
- "redis.call('publish', KEYS[2], ARGV[1]); " +
- "return 1; " +
- "end; " +
- "return 0; ",
- Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage);
- }
没有之前续命的操作了。并且整个删除
RedissonWriteLock
- @Override
- public RFuture<Boolean> forceUnlockAsync() {
- cancelExpirationRenewal(null);
- return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
- "if (redis.call('hget', KEYS[1], 'mode') == 'write') then " +
- "redis.call('del', KEYS[1]); " +
- "redis.call('publish', KEYS[2], ARGV[1]); " +
- "return 1; " +
- "end; " +
- "return 0; ",
- Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.readUnlockMessage);
- }
- @Override
- public boolean isLocked() {
- RFuture<String> future = commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.HGET, getName(), "mode");
- String res = get(future);
- return "write".equals(res);
- }
这两方法和读锁类似就不说了,而且增加过期时间写锁不支持这功能
- @Override
- <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
- internalLockLeaseTime = unit.toMillis(leaseTime);
- return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
- "local mode = redis.call('hget', KEYS[1], 'mode'); " +
- "if (mode == false) then " +
- "redis.call('hset', KEYS[1], 'mode', 'write'); " +
- "redis.call('hset', KEYS[1], ARGV[2], 1); " +
- "redis.call('pexpire', KEYS[1], ARGV[1]); " +
- "return nil; " +
- "end; " +
- "if (mode == 'write') then " +
- "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
- "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
- "local currentExpire = redis.call('pttl', KEYS[1]); " +
- "redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
- "return nil; " +
- "end; " +
- "end;" +
- "return redis.call('pttl', KEYS[1]);",
- Arrays.<Object>asList(getName()),
- internalLockLeaseTime, getLockName(threadId));
- }
显然,如果写锁是这个线程持有的才可以进行写操作。
- @Override
- protected RFuture<Boolean> unlockInnerAsync(long threadId) {
- String timeoutPrefix = getReadWriteTimeoutNamePrefix(threadId);
- String keyPrefix = getKeyPrefix(threadId, timeoutPrefix);
- return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
- "local mode = redis.call('hget', KEYS[1], 'mode'); " +
- "if (mode == false) then " +
- "redis.call('publish', KEYS[2], ARGV[1]); " +
- "return 1; " +
- "end; " +
- "local lockExists = redis.call('hexists', KEYS[1], ARGV[2]); " +
- "if (lockExists == 0) then " +
- "return nil;" +
- "end; " +
- "local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " +
- "if (counter == 0) then " +
- "redis.call('hdel', KEYS[1], ARGV[2]); " +
- "end;" +
- "redis.call('del', KEYS[3] .. ':' .. (counter+1)); " +
- "if (redis.call('hlen', KEYS[1]) > 1) then " +
- "local maxRemainTime = -3; " +
- "local keys = redis.call('hkeys', KEYS[1]); " +
- "for n, key in ipairs(keys) do " +
- "counter = tonumber(redis.call('hget', KEYS[1], key)); " +
- "if type(counter) == 'number' then " +
- "for i=counter, 1, -1 do " +
- "local remainTime = redis.call('pttl', KEYS[4] .. ':' .. key .. ':rwlock_timeout:' .. i); " +
- "maxRemainTime = math.max(remainTime, maxRemainTime);" +
- "end; " +
- "end; " +
- "end; " +
- "if maxRemainTime > 0 then " +
- "redis.call('pexpire', KEYS[1], maxRemainTime); " +
- "return 0; " +
- "end;" +
- "if mode == 'write' then " +
- "return 0;" +
- "end; " +
- "end; " +
- "redis.call('del', KEYS[1]); " +
- "redis.call('publish', KEYS[2], ARGV[1]); " +
- "return 1; ",
- Arrays.<Object>asList(getName(), getChannelName(), timeoutPrefix, keyPrefix),
- LockPubSub.unlockMessage, getLockName(threadId));
- }
这里的也是给读写锁续命,看来就是数据使用次数越多读写锁存活的时间越长,而具体的读锁写锁的存活时间则是hashmap里面的一个属性。
redis 读写锁实现的更多相关文章
- redis 分布式读写锁
http://zhangtielei.com/posts/blog-redlock-reasoning.html 链接里这篇 blog 讨论了 redis 分布式锁的实现以及安全性 我要参考 基于单R ...
- Java之——redis并发读写锁,使用Redisson实现分布式锁
原文:http://blog.csdn.net/l1028386804/article/details/73523810 1. 可重入锁(Reentrant Lock) Redisson的分布式可重入 ...
- 1.3.2 AQS 读写锁
1.读写锁原理 2.利用读写锁写一个安全的HashMap 读写锁原理 ReadWriteLock:维护一对关联锁,一个读锁一个写锁,读锁可以由多个线程同时获得,写锁只能被一个线程获得.同一时间,读锁和 ...
- 【分布式锁】07-Zookeeper实现分布式锁:Semaphore、读写锁实现原理
前言 前面已经讲解了Zookeeper可重入锁的实现原理,自己对分布式锁也有了更深的认知. 我在公众号中发了一个疑问,相比于Redis来说,Zookeeper的实现方式要更好一些,即便Redis作者实 ...
- 从自旋锁、睡眠锁、读写锁到 Linux RCU 机制讲解
同步自我的 csdn 博客 6.S081 从自旋锁.睡眠锁.读写锁到 Linux RCU 机制讲解_我说我谁呢 --CSDN博客 总结一下 O/S 课程里面和锁相关的内容. 本文是 6.S0 ...
- Java 读写锁 ReadWriteLock 原理与应用场景详解
Java并发编程提供了读写锁,主要用于读多写少的场景,今天我就重点来讲解读写锁的底层实现原理@mikechen 什么是读写锁? 读写锁并不是JAVA所特有的读写锁(Readers-Writer Loc ...
- 技术笔记:Delphi多线程应用读写锁
在多线程应用中锁是一个很简单又很复杂的技术,之所以要用到锁是因为在多进程/线程环境下,一段代码可能会被同时访问到,如果这段代码涉及到了共享资源(数据)就需要保证数据的正确性.也就是所谓的线程安全.之前 ...
- java多线程-读写锁
Java5 在 java.util.concurrent 包中已经包含了读写锁.尽管如此,我们还是应该了解其实现背后的原理. 读/写锁的 Java 实现(Read / Write Lock Java ...
- 让C#轻松实现读写锁分离
ReaderWriterLockSlim 类 表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问. 使用 ReaderWriterLockSlim 来保护由多个线程读取但每次只采用一 ...
随机推荐
- .Net Core 使用Session
1. NUGET包引用 icrosoft.AspNetCore.Session 2.Startup中添加一下代码: public void ConfigureServices(IServiceColl ...
- Linq to Objects for Java
好几年不写博客了,人也慢慢变懒了.然而想写了却不知道写点啥,正好最近手头有点小项目就分享一下经历. 现在 java 的大环境下,基本都是围着 spring 转,加上一堆其他的库.有了架子就开始搞业务了 ...
- 正经学C#_位移与其位移运算符[c#入门经典]
在c#入门经典一书中,最为糟糕的一节就是位移了,完全没有讲明白,也没有说全,似乎只是轻轻点了一下何为位移,带了两次原码和补码,完全不理会是否明白不明白.这一点这本书很差.因为此书说了,在大多数应用开发 ...
- CSS效果:图片切换
HTML: <html lang="en"> <head> <meta charset="UTF-8"> <meta ...
- 用SQL玩转数据挖掘之MADlib(一)——安装
一.MADlib简介 MADlib是Pivotal公司与伯克利大学合作的一个开源机器学习库,提供了精确的数据并行实现.统计和机器学习方法对结构化和非结构化数据进行分析,主要目的是扩展数据库的分析能力, ...
- Java基础笔记(十九)——抽象类abstract
抽象类作为父类,不能实例化自己类型的对象,但可以通过向上转型实例化子类对象. public abstract class Animal{ } 比如eat(); ,每个动物子类都应有自己的方法,那An ...
- Pre- and Post-order Traversals(先序+后序序列,建立二叉树)
PAT甲级1119,我先在CSDN上面发布的这篇文章:https://blog.csdn.net/weixin_44385565/article/details/89737224 Suppose th ...
- sshd服务及系统文件传输
一.sshd 简介 sshd= secure shell 可以通过网络在主机中开机shell的服务 客户端软件 sshd 连接方式: ssh username@ip ##文本模式的链 ...
- Linux链接器脚本详解
/* GNU linker script for STM32F405 */ /* Specify the memory areas */ MEMORY { FLASH (rx) : ORIGIN = ...
- 事物及exec
事物3要出不多讲: 1.BEGIN TRANSACTION--开启事务 2.COMMIT TRANSACTION--事务执行 3.ROLLBACK TRANSACTION--事务回滚 俩总捕捉事物的方 ...