为什么需要读写锁
简单来说,一般的锁只能让一个线程进行占用,别的线程只能进行等待,但是在实际开发的过程中,我们往往都是读多写少的情况,希望可以一起进行读,写的时候可以进行加锁。总结起来就是:读读不互斥,读写互斥,写写互斥
jdk版本的读写锁
jdk版本提供了一个ReadWriteLock类,jdk中的主要实现类为ReentrantReadWriteLock,里面有两个类ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock进行创建读锁和写锁
ReentrantReadWriteLock的核心是由一个基于AQS的同步器Sync
构成,然后由其扩展 出 ReadLock
(读锁),WriteLock
(写锁)
ReentrantReadWriteLock构造函数
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
/**
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
从上面的代码可以看出来,ReentrantReadWriteLock是通过state这个int字段的高低16位来进行表示共享锁和独占锁的数量,高16位表示共享锁的数,低16位表示独占锁的数量
读锁的获取
//获取读锁开始
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
//尝试进行获取锁
if (tryAcquireShared(arg) < 0)
//获取失败之后进行添加到AQS队列中
doAcquireShared(arg);
}
tryAcquireShared获取读锁
protected final int tryAcquireShared(int unused) {
//获取当前线程
Thread current = Thread.currentThread();
//获取当前的状态 读写锁中通过state状态进行判断加锁的状态
int c = getState();
//进行判断是否是独占锁,并且独占锁的线程是当前线程
// 如果是独占锁 并且独占锁的线程不是当前线程就直接返回-1
// 返回-1 就直接进入队列进行排队的操作
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return -1;
//返回持久共享锁的数量
int r = sharedCount(c);
/***
* readerShouldBlock判断是否可以获取共享锁
* r < MAX_COUNT 共享锁的数量是否小于最大的锁数量
* 采用cas操作进行设置共享锁的数量
* 设置读锁的数量
***/
if (!readerShouldBlock() &&r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
//读锁数量=0
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//真正的获取读锁的操作
return fullTryAcquireShared(current);
}
fullTryAcquireShared
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
// 最外层是一个死循环
for (;;) {
//获取当前锁的状态
int c = getState();
//存在写锁并且写锁不是当前线程直接返回
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {
//如果当前线程是重入锁
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
//读锁数量的判断
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//设置锁的数量
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
读锁的释放
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//进行读锁的释放
if (tryReleaseShared(arg)) {
doReleaseShared();// 读锁释放成功之后进行释放信号 用来唤醒等待
return true;
}
return false;
}
读锁真正的释放代码
protected final boolean tryReleaseShared(int unused) {
//针对读锁添加的时候操作进行反操作
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
//cas操作进行更改读锁的状态和数量
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
写锁的获取
//获取写锁
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
//tryAcquire 尝试进行获取写锁
// 获取写锁失败之后 进行添加到队列中
// 将当前线程设置成interrupt状态
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
//获取当前线程
Thread current = Thread.currentThread();
//获取当前锁的状态
int c = getState();
//获取写锁的数量
int w = exclusiveCount(c);
// c!=0 证明当前要么存在读锁 要么存在写锁
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
// 存在锁,写锁数量为0 那么证明 读锁数量不为0 并且 当前线程还不是加锁线程
//直接返回失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//数量超限制直接抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
// 执行到这里 证明存在锁 并且是一个写锁 并且当前线程就是加锁的线程
//而且加锁的数量也没超过最大的数量 就可以直接进行加锁
//可以直接进行修改锁的数量。不需要进行cas之类的操作 因为都是同一个线程
setState(c + acquires);
return true;
}
//writerShouldBlock对于非公平模式直接返回fasle,对于公平模式则线程需要排队
// 因此在这里进行阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
写锁的释放
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
//写锁释放成功之后 进行唤醒队列中第一个元素
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease方法
protected final boolean tryRelease(int releases) {
// 判断当前线程是否可以进行写锁的释放 如果不可以
//直接抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//写锁数量的更新
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
//当写锁的数量为0的时候 将写锁的拥有者的线程设置为null;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
redis分布式读写锁
redis分布式锁的使用的redission框架进行读写锁的配置,
RReadWriteLock rReadWriteLock = redissonClient.getReadWriteLock("rwlock");
RLock readLock = rReadWriteLock.readLock();
readLock.lock();
readLock.unlock();
RLock writeLock = rReadWriteLock.writeLock();
writeLock.lock();
writeLock.unlock();
#读写锁的初始化
public class RedissonReadWriteLock extends RedissonExpirable implements RReadWriteLock {
public RedissonReadWriteLock(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
}
@Override
public RLock readLock() {
return new RedissonReadLock(commandExecutor, getName());
}
@Override
public RLock writeLock() {
return new RedissonWriteLock(commandExecutor, getName());
}
}
redission分布式锁的读锁
分布式锁读锁的加锁设置
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, command,
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
//获取 hash列表中的mode字段的值
"if (mode == false) then " +
//如果hash表中的mode的值为false的时候 设置mode值为read
"redis.call('hset', KEYS[1], 'mode', 'read'); " + //设置hash表中的 锁的添加次数为1
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
// 设置锁的缓存key
"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; " +
// 如果mode值为read
// 如果mode值为write 并且加写锁的线程为当前线程
"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]); " +
"local remainTime = redis.call('pttl', KEYS[1]); " +
"redis.call('pexpire', KEYS[1], math.max(remainTime, ARGV[1])); " +
"return nil; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
Arrays.<Object>asList(getRawName(), getReadWriteTimeoutNamePrefix(threadId)),
unit.toMillis(leaseTime), getLockName(threadId), getWriteLockName(threadId));
}
从上面的代码可以看出来,redission提供的读写锁并不是完全的互斥,当同一个线程先获取写锁还是可以再次获取读锁的
分布式锁读锁的释放操作
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
String timeoutPrefix = getReadWriteTimeoutNamePrefix(threadId);
String keyPrefix = getKeyPrefix(threadId, timeoutPrefix);
return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
//获取当前读写锁的模式
// 如果锁为false 直接进行发送锁释放的消息
"if (mode == false) then " +
"redis.call(ARGV[3], KEYS[2], ARGV[1]); " +
"return 1; " +
"end; " +
//判断当前是否存在 如果不存在直接返回null
"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 就直接进行删除key的操作
"if (counter == 0) then " +
"redis.call('hdel', KEYS[1], ARGV[2]); " +
"end;" +
// 删除锁对应的加锁记录
"redis.call('del', KEYS[3] .. ':' .. (counter+1)); " +
// 获取map中的key的数量
"if (redis.call('hlen', KEYS[1]) > 1) then " +
"local maxRemainTime = -3; " +
// 获取map中key的集合
"local keys = redis.call('hkeys', KEYS[1]); " +
//针对map的key进行遍历操作
"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; " +
//如果剩余的时间大于0 更新锁的过期时间为最大的过期时间
"if maxRemainTime > 0 then " +
"redis.call('pexpire', KEYS[1], maxRemainTime); " +
"return 0; " +
"end;" +
//如果是模式是写锁 直接返回0
"if mode == 'write' then " +
"return 0;" +
"end; " +
"end; " +
//走到这里 证明了锁已经释放 并且没有别的线程持有这个锁了
// 可以直接删除锁的key
"redis.call('del', KEYS[1]); " +
// 发送unlock的消息
"redis.call(ARGV[3], KEYS[2], ARGV[1]); " +
"return 1; ",
Arrays.<Object>asList(getRawName(), getChannelName(), timeoutPrefix, keyPrefix),
LockPubSub.UNLOCK_MESSAGE, getLockName(threadId), getSubscribeService().getPublishCommand());
}
redission分布式锁的写锁
redission写锁的添加
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, command,
//获取锁的模式
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
//判断mode的是否为false
"if (mode == false) then " +
//如果是false 设置mode的值为write
"redis.call('hset', KEYS[1], 'mode', 'write'); " +
//设置当前锁的线程id
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
//设置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
//判断mode是否为write
"if (mode == 'write') then " +
//判断是否有当前线程添加的redis锁
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
//存在进行加锁的次数进行+1
"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(getRawName()),
unit.toMillis(leaseTime), getLockName(threadId));
}
redisssion分布式写锁的释放
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
//获取redis中的key中的mode的值
"if (mode == false) then " +
//mode的值为false的时候 进行通知
"redis.call(ARGV[4], KEYS[2], ARGV[1]); " +
//返回1
"return 1; " +
"end;" +
// 如果mode为write 证明redis中已经加上了写锁
"if (mode == 'write') then " +
//判断是否有当前线程添加的锁
"local lockExists = redis.call('hexists', KEYS[1], ARGV[3]); " +
//没有的话 就直接返回
"if (lockExists == 0) then " +
"return nil;" +
"else " +
// 进行获取加锁的次数
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
//加锁的次数大于0
"if (counter > 0) then " +
//设置key的过期时间为internalLockLeaseTime的值
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
//加锁的次数等于0 不存在小于0的情况 直接删除绑定线程的key
"redis.call('hdel', KEYS[1], ARGV[3]); " +
// 判断此时hash结构中的key的数量
"if (redis.call('hlen', KEYS[1]) == 1) then " +
//如果数量等于1 证明只有一个加锁 可以直接删除这个key了
"redis.call('del', KEYS[1]); " +
"redis.call(ARGV[4], KEYS[2], ARGV[1]); " +
"else " +
//到这块的话,证明写锁已经全部释放完毕了
//但是此时还有一个加锁的 那么此时加的这个锁就不是写锁
//只能是读锁,所以将redis中的锁设置为读锁
// has unlocked read-locks
"redis.call('hset', KEYS[1], 'mode', 'read'); " +
"end; " +
"return 1; "+
"end; " +
"end; " +
"end; "
+ "return nil;",
Arrays.<Object>asList(getRawName(), getChannelName()),
LockPubSub.READ_UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId), getSubscribeService().getPublishCommand());
}