这个类是juc提供的读写锁,多个读操作可以同时加锁,写操作同时只能有一个线程加锁,也就是说读锁是共享的,写锁是互斥的,读锁和写锁之间也是互斥的。
AQS维护了state表示资源获取的情况,读写锁用这一个表示读锁和写锁的资源获取情况,高16位表示读锁的资源获取情况,低16位表示写锁的资源获取情况。
static final int SHARED_SHIFT = 16;//读锁获取的共享资源是state右移16static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//写锁获取的排他资源是低16位static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
读锁
首先看一下读锁ReadLock的加锁方法,会调用到AQS的acquireShared方法
public void lock() {
sync.acquireShared(1);
}
AQS的acquireShared方法,关键看tryAcquireShared怎么实现的
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
ReentrantReadWriteLock内部Sync实现了AQS,tryAcquireShared方法是Sync实现的,主要内容分为3步
- 判断当前是否被写锁锁定,如果是,判断写锁线程是否是当前线程,如果不是,返回-1,表示没有拿到资源,如果是,往下执行。
- 获取共享锁获取的资源数,如果满足readerShouldBlock是false、资源数小于最大资源数、CAS修改成功,则进入里面代码块。里面代码块首先判断r==0,说明当前没有别的读锁,则当前线程就是第一个读者线程;否则判断当前线程是不是第一个读者,如果是,就将重入数+1;否则,就获取当前线程的HoldCounter,如果cachedHoldCounter不是当前线程的HoldCounter,就从readHolds获取,readHolds是一个ThreadLocal对象,获取到holdCounter后,将当前线程获取的资源数+1。
- 如果第2步的条件判断不满足,则进入fullTryAcquireShared方法。
protected final int tryAcquireShared(int unused) {
//判断当前线程是否获取排他锁
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 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方法第一步也是判断当前是否加了写锁,如果加了,是否是当前线程加的,如果不是,返回-1,没有获取到资源。
然后判断读者是否应该阻塞,如果应该阻塞,就判断当前线程是不是第一个读者,如果是,就说明当前读者已经获取到锁,是重入,允许向下执行;如果当前线程不是第一个读者,获取当前线程的holdCounter,如果等于0,说明当前线程还没有获取读锁,应该阻塞,返回-1,表示没有获取到资源。
然后判断读锁是否已经达到上限,如果是,抛出异常。
最后CAS修改资源,修改获取资源数。
final int fullTryAcquireShared(Thread current) {
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;
}
}
}
写锁
写锁WriteLock的锁方法会调用AQS的acquire方法。
public void lock() {
sync.acquire(1);
}
AQS的acquire方法会尝试获取资源,关键就在尝试获取资源的tryAcquire方法里面。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire里面有两个变量c和w,c表示当前资源数,w表示当前写资源数。
如果c不是0,表示当前有线程获取了资源,判断w是否等于0,如果w等于0,说明当前读锁获取了资源,写锁没有获取资源,获取资源失败;如果w不等于0,判断当前线程是否是获取写锁的线程,如果不是,获取资源失败。
如果可以获取到锁资源,判断锁资源是否达到最大值,如果达到,报错。
如果没达到,就增加资源数,返回true表示获取到了资源。
如果c等于0,判断写者是否需要等待,如果写者需要等待,或者CAS修改失败,返回false,表示没有获取到资源。
否则,设置获取资源的线程为当前线程,返回true,表示获取到了资源。
protected final boolean tryAcquire(int acquires) {
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)
//表示加了读锁,或者写锁不是当前线程加的 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;}
公平和非公平
上面我们看到判断读者是否应该阻塞,写者是否应该阻塞,ReentrantReadWriteLock和ReentrantLock一样,都支持公平锁和非公平锁。读写锁的公平和非公平就是急于readerShouldBlock、writerShouldBlock来实现的。
公平,在公平FairSync里面,读者、写者判断是否需要阻塞都是判断当前线程是否有前面节点,如果有,就暂时不能获取资源,阻塞。
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
非公平,在非公平NonfairSync里面,写者直接返回false,表示写者不需要阻塞;读者判断同步队列的第一个等待的节点是否是写者线程,如果是,需要阻塞,如果不是,不需要阻塞。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
总结
同一个线程获取了写锁还可以获取读锁。