由于ReentrantLook锁是独占锁,同时只允许一个线程进行访问,那么如果存在如下场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作。
针对这种场景,java中提供了ReentrantReadWriteLock来实现,它包含了两个锁实现,一个ReadLock是共享锁,WriteLock是独立锁。下面分析一下读写锁的实现思路。
使用场景
读写锁的使用demo中,doug Lea给出了两种场景,第一种是CacheData典型的多读少写的场景,第二种是RWDictionary 。
class RWDictionary {
private final Map<String, Data> m = new TreeMap<String, Data>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public Data get(String key) {
r.lock();
try {
return m.get(key);
} finally {
r.unlock();
}
}
public String[] allKeys() {
r.lock();
try {
return m.keySet().toArray();
}
finally {
r.unlock();
}
}
public Data put(String key, Data value) {
w.lock();
try {
return m.put(key, value);
} finally {
w.unlock();
}
}
public void clear() {
w.lock();
try {
m.clear();
} finally {
w.unlock();
}
}
}
读写锁定义
在读写锁中,同样分为公平锁与非公平锁,默认使用非公平锁.
//ReentrantReadWriteLock的数据结构定义
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
/** 读锁*/
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 写锁*/
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
//非公平锁实现
static final class NonfairSync extends Sync {
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
//公平锁实现
static final class FairSync extends Sync {
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
//读写锁默认为非公平锁
public ReentrantReadWriteLock() {
this(false);
}
//锁实现的基类,读锁与写锁分别实现些基类
//读锁与写锁最大可以各存储65535个锁.
abstract static class Sync extends AbstractQueuedSynchronizer {
static final int SHARED_SHIFT = 16;
//高16位存储读锁(共享锁)的数量
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//单个锁最大可用数据65535.
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//低16位存储写锁数量的掩码
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
}
}
WriteLock实现(独占锁)
- WriteLock.lock入口
WriteLock加锁部分任然是直接调用AQS.acquire来获取锁。
//ReentrantReadWriteLock中的独占锁WriteLock实现加锁的流程
public static class WriteLock implements Lock, Serializable {
//这里直接调用了对应公平锁或者非公平锁的acquire来进行加锁.
public void lock() {
//调用AQS的acquire函数,此函数会执行如下几个流程:
//=>1,"tryAcquire"对当前线程进行加锁,如果加锁成功,直接结束流程,否则执行步骤2.
//=>2,"addWaiter"线程加锁失败,把线程添加到等待队列中(队列尾部),并执行步骤3.
//=>3,"acquireQueued"线程不断尝试抢占锁资源,如果抢占锁成功结束流程,否则执行步骤4.
//=>4,"selfInterrupt"线程处于BLOCKED状态,
//===>同时被标记中断(读取中断标记后标记位会被清理),重新把线程标记为中断状态.
sync.acquire(1);
}
}
2.tryAcquire函数实现
线程进入WriteLock锁的条件:
1,当前锁资源没有被任何线程持有(即没有读锁也没有写锁)
2,如果锁资源已经被线程持有,这个持有者线程必须是当前线程本身(不能是共享锁).
3,WriteLock锁的重入次数最大不能超过65535次。
当线程抢占锁资源失败,返回false,由AQS添加到等待队列等待唤醒,并重复执行此步骤。
//ReentrantReadWriteLock.Sync.tryAcquire实现锁资源抢占实现
protected final boolean tryAcquire(int acquires) {
//先获取锁的状态位标记属性state.
Thread current = Thread.currentThread();
int c = getState();
//计算当前写锁的总锁数量(c变量的低16位)"c & EXCLUSIVE_MASK"
int w = exclusiveCount(c);
if (c != 0) {
//"c != 0 && w == 0"表示没有WriteLock锁,只有ReadLock的共享锁.
//==>锁的线程持有者不是当前线程,表示抢占锁资源失败,返回false.
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//流程执行到这里,表示锁的持有者就是当前线程本身.
//==>判断WriteLock的锁数量是否大于锁的最大记录数(65535),不能超过65535个WriteLock锁。
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//线程重入锁(当前线程就是持有锁的线程),并记录WriteLock的数量加1.
setState(c + acquires);
return true;
}
//没有任何线程持有锁(state==0),这里分为两种情况:
//1,非公平锁:执行"compareAndSetState(c, c + acquires)",
//==>直接对锁数量的状态位进行CAS操作,失败表示抢占锁失败,直接返回false结束流程.
//2,公平锁:先执行"writerShouldBlock()",
//==>判断等待队列的中当前线程的前面是否还有等待的线程,
//====>如果有返回true表示抢占锁失败,直接返回false结束流程.
//====>如果等待队列为空,或者等待队列的队顶元素就是当前线程,说明可以重入,执行下一歩操作:
//==>执行"compareAndSetState(c, c + acquires)",
//====>直接对锁数量的状态位进行CAS操作,失败表示抢占锁失败,直接返回false结束流程.
//====>这种情况只有一种场景会失败,等待队列为空,同一时间两个线程来抢占锁资源.
if (writerShouldBlock() ||!compareAndSetState(c, c + acquires)) {
return false;
}
//当前线程抢占锁成功,设置当前线程为WriteLock的所有者,并返回true.
setExclusiveOwnerThread(current);
return true;
}
//公平锁的writerShouldBlock函数,判断是否需要阻止线程获取写锁.
//注意:非公平锁默认不阻止线程获取写锁,即:函数在非公平锁时直接返回false.
final boolean writerShouldBlock() {
//直接调用AQS的hasQueuedPredecessors函数。
//这里判断队列是否为空或者队列的队顶元素是否是当前线程,是当前线程返回false.
//==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.
return hasQueuedPredecessors();
}
//AQS.hasQueuedPredecessors,判断队顶元素是否是当前线程,如果不是当前线程返回true.
//==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
3.WriteLock释放锁
WriteLock锁是独占锁,因此释放锁的入口是直接调用AQS.release来进行释放,而AQS.release依赖锁具体实现的tryRelease函数来释放锁资源。
//WriteLock.unlock释放锁资源与独占锁的释放相同,直接走AQS.release.
public void unlock() {
//AQS.release依赖当前Lock接口中的tryRelease函数.
sync.release(1);
}
//Sync.tryRelease函数,完成当前线程锁的释放,
//==>返回true表示当前线程持有的锁资源已经完全释放.
//==>返回false表示线程有重入锁还未释放完.
protected final boolean tryRelease(int releases) {
//1,先判断当前线程是否是锁的owner,必须是当前锁持有者才能释放锁.
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//对锁持有数量减1(直接减是减低16位),即:对WriteLock的持有数减1.
int nextc = getState() - releases;
//判断写锁的持有数量(低16位)是否为0,"c & EXCLUSIVE_MASK;"
//注意:这里只判断低16位,不判断高16位(即读锁不管)
//锁持有数量为0表示锁释放完成,否则表示有重入锁还未完全释放返回false.
boolean free = exclusiveCount(nextc) == 0;
//如果锁释放完成,清空独占锁的线程owner
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
ReadLock实现(共享锁)
1.ReadLock.lock入口
//ReadLock.lock加锁流程,这里是共享锁,因此调用流程与独占锁不同
public void lock() {
//直接调用AQS.acquireShared来获取共享锁资源.
sync.acquireShared(1);
}
2,tryAcquireShared
此函数用于获取共享锁资源,由AQS负责调用。由ReentrantReadWriteLock.Sync进行实现。
在共享锁的实现中包含了如下几个变量:
firstReader/firstReaderHoldCount : 首次获取到共享锁的线程与锁计数器.
cachedHoldCounter : 最后一次获取共享锁的计数器实例(HoldCounter).
==>这个计数器中存储了最后一个获取锁的线程ID与锁计数,在超过一个线程获取共享锁时,时变量存在。
readHolds : 继承与ThreadLocal的ThreadLocalHoldCounter实例,用于维护每个持有共享锁的线程计数器.
这个函数主要完成如下几个场景:
1,资源有被写锁持有,但持有写锁的线程不是当前线程,返回-1,表示获取读锁失败,需要添加到等待队列中交由AQS的doAcquireShared来处理。
2,“readerShouldBlock()”不阻止线程获取读取锁,同时读取锁数据小于65535,同时CAS设置读取锁计数器成功(在原值上加1),表示获取读取锁成功,根据holdCount计数器.
3,其它情况调用”fullTryAcquireShared”,CAS自旋不断尝试获取锁资源。这里有一个锁降级的概率,其实就是当前线程如果持有写锁,那么线程要获取共享锁时可以直接重入,减少一次释放写锁在重新抢占读锁的过程。
//Sync.tryAcquireShared尝试获取共享锁资源,
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//1,如果有线程持有写锁资源,同时写锁的持有者非当前线程,直接结束流程返回-1.
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return -1;
//2,获取共享锁的数量“c >>> SHARED_SHIFT;”,计算高16位的值.
int r = sharedCount(c);
//=>a,"!readerShouldBlock()"判断是否应该阻止线程获取读锁(共享锁),
//====>返回false表示不阻止
//=>b,"r < MAX_COUNT"如果共享锁的数据不超过65535个,表示可以继续申请锁资源.
//=>c,"compareAndSetState(c, c + SHARED_UNIT)",CAS设置state高16位加1,
//====>设置成功表示当前线程抢占到资源
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//流程执行到这里,说明当前线程成功获取到读取锁.
if (r == 0) {
//"r == 0",当前没有线程持有读锁,说明当前线程是第一个读取锁的持有者.
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//第一个读取锁持有者就是当前线程,读取锁计数器加1.
firstReaderHoldCount++;
} else {
//“cachedHoldCounter”上一次获取到读取锁的线程计数器.
HoldCounter rh = cachedHoldCounter;
//"rh == null",首次非firstReader线程进入.
if (rh == null || rh.tid != getThreadId(current)) {
//获取当前线程对应的"HoldCounter"实例
//==>这是一个继承ThreadLocal的的类,"readHolds.get()"请参考ThreadLocal.get实现
//==>readHolds是“ThreadLocalHoldCounter”的实例继承与ThreadLocal。
//==>如果当前线程没有初始化,会调用"initialValue"生成一个新的线程计数器.
cachedHoldCounter = rh = readHolds.get();
} else if (rh.count == 0) {
//此时“rh != null”,同时rh.count计数器是0,用上个线程的HoldCounter覆盖当前线程.
readHolds.set(rh);
}
//线程读取锁计数器加1.
rh.count++;
}
//成功获取到读取锁,返回1结束流程.
return 1;
}
//3,最后一种情况,CAS自旋不断尝试获得读取锁,
//==>如果写锁的持有者是当前线程本身时会进行锁降级,(即重入锁然后释放写锁后就只剩下读取锁)
return fullTryAcquireShared(current);
}
3,readerShouldBlock
此函数的作用是判断当前线程是否阻止获取共享锁,返回false表示可以获取共享锁。
函数的实现分为非公平锁与公平锁。
非公平锁的实现:
非公平锁模式下,如果队顶元素是独占锁时,阻止当前线程获取共享锁.
//NonfairSync.readerShouldBlock非公平锁判断是否需要阻止获取共享锁,
final boolean readerShouldBlock() {
//调用AQS中的函数来判断队顶元素是否是独占锁线程,如果是返回true.
return apparentlyFirstQueuedIsExclusive();
}
//AQS.apparentlyFirstQueuedIsExclusive判断队顶元素是否是独占锁,如果是返回true.
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
公平锁的实现:
公平锁模式下,判断等待队列中当前线程是否还有前继节点,如果有返回true,否则返回false.
//FairSync.readerShouldBlock公平锁判断是否需要阻止获取共享锁,
final boolean readerShouldBlock() {
//调用AQS.hasQueuedPredecessors判断队顶元素是否是当前线程,
//==>如果不是当前线程返回true,表示阻止线程获取共享锁
//==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.
return hasQueuedPredecessors();
}
//AQS.hasQueuedPredecessors,判断队顶元素是否是当前线程,如果不是当前线程返回true.
//==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
4,fullTryAcquireShared
CAS自旋尝试获取共享锁,几种情况下可以获取到共享锁:
- 写锁持有线程是当前线程。
- 写锁是空闲状态,即使是有其它线程持有共享锁,也可以获取到共享锁。
- 其它情况失败,返回-1,交由AQS的doAcquireShared来处理。
//Sync.fullTryAcquireShared,CAS自旋锁不断尝试获取共享锁.
final int fullTryAcquireShared(Thread current) {
//CAS自旋,不断迭代.
HoldCounter rh = null;
for (;;) {
int c = getState();
//如果写锁持有者不是当前线程,表达获取锁失败,直接返回-1.
//相反写锁持有者就是当前线程,继续执行后面的流程.
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here would cause deadlock.
//时此"exclusiveCount(c) == 0",这时表示写锁是空闲状态(没有线程持有写锁)
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
//第一个共享锁的持有者就是当前线程.
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
//第一个共享锁持有者不是当前线程,
if (rh == null) {
//刷新cachedHoldCounter(最后一个共享锁的持有者)
rh = cachedHoldCounter;
//如果最后一个共享锁的持有线程不是当前线程,得到当前线程的HoldCounter计数器
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
//当前线程的HoldCounter计数器为0,表示线程没有持有任何共享锁,
//==>从ThreadLocalMap中移出此计数器
if (rh.count == 0)
readHolds.remove();
}
}
//当前线程的HoldCounter计数器为0,直接返回-1,让AQS把线程添加到等待队列中.
if (rh.count == 0)
return -1;
}
}
//共享锁的持有数量超过最大值.
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//CAS尝试设置sharedState状态值,即:把共享锁数量加1,如果成功表示获取锁成功
//==>1,此时当前线程可能本身持有写锁,重入共享锁(锁降级)
//==>2,资源写锁空闲,即使是有其它线程持有共享锁(readerShouldBlock()==true),也可以获取锁.
if (compareAndSetState(c, c + SHARED_UNIT)) {
//如果当前线程是第一个获取共享锁的线程,设置firstReader为当前线程.
//==>对firstReaderHoldCount计数器加1.
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//当前线程是首次获取共享锁的线程,firstReaderHoldCount计数器加1.
firstReaderHoldCount++;
} else {
//更新最后一个共享锁持有者,"cachedHoldCounter"计数器缓存.
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
//HoldCounter计数器加1.
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
5,锁的释放
ReadLock中锁的释放同样直接调用“AQS.releaseShared”的释放锁来进行处理。
请参考“AQS的releaseShared”实现。
//ReentrantReadWriteLock.ReadLock的锁释放流程
public void unlock() {
//直接调用AQS.releaseShared释放共享锁.
//1,调用Sync具体实现中的tryReleaseShared释放锁.
//2,如果锁释放成功,调用doReleaseShared唤醒后继节点.
sync.releaseShared(1);
}
tryReleaseShared函数:
此函数完成共享锁的释放流程.
//Sync.tryReleaseShared释放共享锁资源的具体实现
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
//如果当前线程是第一个持有共享锁资源的线程,分为两种情况:
//1,线程只持有一个共享锁,直接设置firstReader为null.
//2,线程持有多个共享锁句柄,firstReaderHoldCount对应减1.
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
//1,先判断当前线程是否是最后一个获取共享锁的线程,
//==>如果不是,从readHolds的ThreadLocalMap中获取当前线程对应的HoldCounter.
//2,判断线程持有共享锁的句柄个数,如果只有一个,直接从readHolds移出此HoldCounter.
//==>如果线程持有多个共享锁的句柄,对应HoldCounter.count减1.
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自旋锁,修改state值高16位共享锁的数量,把共享锁的数量减1.
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
//当锁资源完全释放完成后(没有读取锁也没有写锁),此函数返回true,否则返回false.
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;
}
}