先搞清楚读写锁要做什么。

基本就是
读读不互斥,读写互斥,写写互斥。可重入。

关于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 读写锁实现的更多相关文章

  1. redis 分布式读写锁

    http://zhangtielei.com/posts/blog-redlock-reasoning.html 链接里这篇 blog 讨论了 redis 分布式锁的实现以及安全性 我要参考 基于单R ...

  2. Java之——redis并发读写锁,使用Redisson实现分布式锁

    原文:http://blog.csdn.net/l1028386804/article/details/73523810 1. 可重入锁(Reentrant Lock) Redisson的分布式可重入 ...

  3. 1.3.2 AQS 读写锁

    1.读写锁原理 2.利用读写锁写一个安全的HashMap 读写锁原理 ReadWriteLock:维护一对关联锁,一个读锁一个写锁,读锁可以由多个线程同时获得,写锁只能被一个线程获得.同一时间,读锁和 ...

  4. 【分布式锁】07-Zookeeper实现分布式锁:Semaphore、读写锁实现原理

    前言 前面已经讲解了Zookeeper可重入锁的实现原理,自己对分布式锁也有了更深的认知. 我在公众号中发了一个疑问,相比于Redis来说,Zookeeper的实现方式要更好一些,即便Redis作者实 ...

  5. 从自旋锁、睡眠锁、读写锁到 Linux RCU 机制讲解

    ​    同步自我的 csdn 博客 6.S081 从自旋锁.睡眠锁.读写锁到 Linux RCU 机制讲解_我说我谁呢 --CSDN博客 总结一下 O/S 课程里面和锁相关的内容. 本文是 6.S0 ...

  6. Java 读写锁 ReadWriteLock 原理与应用场景详解

    Java并发编程提供了读写锁,主要用于读多写少的场景,今天我就重点来讲解读写锁的底层实现原理@mikechen 什么是读写锁? 读写锁并不是JAVA所特有的读写锁(Readers-Writer Loc ...

  7. 技术笔记:Delphi多线程应用读写锁

    在多线程应用中锁是一个很简单又很复杂的技术,之所以要用到锁是因为在多进程/线程环境下,一段代码可能会被同时访问到,如果这段代码涉及到了共享资源(数据)就需要保证数据的正确性.也就是所谓的线程安全.之前 ...

  8. java多线程-读写锁

    Java5 在 java.util.concurrent 包中已经包含了读写锁.尽管如此,我们还是应该了解其实现背后的原理. 读/写锁的 Java 实现(Read / Write Lock Java ...

  9. 让C#轻松实现读写锁分离

    ReaderWriterLockSlim 类 表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问. 使用 ReaderWriterLockSlim 来保护由多个线程读取但每次只采用一 ...

随机推荐

  1. Android - AndroidStudio 的熟悉

    开发环境 * JDK * SDK * AndroidStudio * Genimotioin HelloWorld [ 第一个Android项目建立 ] * 创建项目  [ 项目相关目录 ] Hell ...

  2. Installing the .NET Framework 3.5 on Windows 8, Windows 8.1 and Windows 10

    Installing the .NET Framework 3.5 on Windows 8, Windows 8.1 and Windows 10 .NET Framework (current v ...

  3. numpy中argsort函数用法

    在Python中使用help帮助 >>> import numpy >>> help(numpy.argsort) Help on function argsort ...

  4. C#中抽象类与接口

    1抽象类 (1) 抽象方法只作声明,而不包含实现,可以看成是没有实现体的虚方法 (2) 抽象类不能被实例化 (3) 抽象类可以但不是必须有抽象属性和抽象方法,但是一旦有了抽象方法,就一定要把这个类声明 ...

  5. JavaScript——原生js实现瀑布流

    瀑布流介绍及实现原理: 瀑布流是一种页面布局,页面上也有多等宽的块(块就页面内容),每一块都是绝对定位(absolute),每个块排列的方式如下:寻找现在高度最小的列,把该块定位到该列下方.需要知道, ...

  6. P4196 [CQOI2006]凸多边形 半平面交

    \(\color{#0066ff}{题目描述}\) 逆时针给出n个凸多边形的顶点坐标,求它们交的面积.例如n=2时,两个凸多边形如下图: 则相交部分的面积为5.233. \(\color{#0066f ...

  7. linux 虚拟环境问题

    1.python环境 python2和python3命令用来区分python版本 pip2和pip3命令用来区分pip,你的包到底安装在哪里pip3 install xxx sudo apt inst ...

  8. powdesigner建表

    默认打开powerDesigner时,创建table对应的自动生成sql语句没有注释. 方法1.comment注释信息 在Columns标签下,一排按钮中找到倒数第2个按钮:Customize Col ...

  9. Mybatis学习笔记(二) —— mybatis入门程序

    一.mybatis下载 mybaits的代码由github.com管理,下载地址:https://github.com/mybatis/mybatis-3/releases 下载完后的目录结构: 二. ...

  10. SharePoint 2013 设置 显示详细错误信息 修改位置总结

    以80端口为例—— 1.修改:C:\inetpub\wwwroot\wss\VirtualDirectories\80\web.config文件配置 CallStack="false&quo ...