读写锁用于读较写相对频繁的场景,多个读线程可以同时获取读锁而不阻塞,只有一个线程能获取写锁。写锁获取的前提是读锁已经全部被释放,写锁获取后读锁不能再获取(其实是不能被非获取写锁的线程所获取),直到写锁被释放。
jdk中实现Lock接口的读写锁ReentrantReadWriteLock
是如何实现此功能呢?在阅读这篇文章之前,我假设你已经读过可重入锁,对锁的实现有一定的了解。
读写锁同步状态的设计
同步状态是一个volatile修饰的int变量,四个字节,前2个字节用来用来标识读状态,后两个字节用来标识写状态。图示如下:
读写锁状态划分方式(引自并发编程的艺术)图中的示例表示当前线程获取了三次写锁(重进入两次),同时也连续获取了两次读锁。根据图示能够轻易得出结论,当合状态不为0,而写状态为0时,则读状态大于0,读锁已被获取。
写锁的获取与释放
写锁是一个支持重进入的排他锁。当前线程如果已经获取了写锁,则增加写状态;如果当前线程在获取写锁时,读锁已经被获取或者当前线程不是已经获取写锁的线程,则失败进入等待状态。
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//w==0得出读状态不为0
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
//当前节点在写同步队列里是否有前驱节点
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
写锁的释放与可重入锁基本相似,写状态为0时,释放成功;读写线程能够继续访问读写锁,同时上次写线程的修改对后续读写线程可见。
读锁的获取与释放
获取读锁失败的原因:
- 写锁已被获取且当前线程不是获取写锁的线程。
获取读锁成功的原因:
- 写锁已被获取且是当前线程获取的。
- 写锁未被获取。
获取读锁的代码较为复杂,因为增加了得到当前线程获取读锁次数的功能(利用ThreadLocal实现)。有兴趣的你可以直接查看源码,这里暂不做说明。
锁降级
主要阐述两个方面,锁降级是什么概念?锁降级是否有必要?
锁降级是什么概念?线程A获取到写锁后,修改数据后再获取读锁,最后释放写锁,这就是锁降级。造成的直接影响就是线程A释放写锁后,下一个企图获取写锁的线程不会立即获取到写锁,因为至少已经有一个线程A读锁还没有释放。
必要性是有的。如果获取写锁后,你修改了数据,且你希望你修改的数据能被感知到(至少自己是能够感知的),希望线程对数据的变化是极其敏感的,你就一定要在使用读写锁时锁降级。
举个例子。 线程A获取写锁往小王的账号里打了500块钱,释放写锁。线程C获取读锁要把这条打款消息发送到小王手机(XX往你的银行账号XX里打了XX钱)。如果你不使用锁降级,那么线程B可能会在线程A释放写锁后立即获取到写锁,它也给小王打了500块钱,线程B释放写锁后,线程C获取到读锁给小王发信息(YY往你的银行账号YY里打了YY钱),因为线程A修改的数据没有读线程获取读锁进行感知处理,可能会漏掉这一修改信息,直接影响就是小王少收了一条短信。当然,这种必要性是跟实际业务有关的,如果短信信息只是通知小王银行账号里的总额的话,似乎锁降级也没有什么必要。
网友评论