概要
读写锁中,读与写、写与写是互斥的,而 读与读 不是互斥的,所以读写锁在执行写操作的线程没有获得锁的情况下,执行读操作的线程可以多个线程都拥有锁。因此读写锁在存在大量读操作的情况下比普通锁的效率要好。读写锁的读锁的实现主要依赖于AQS的共享模式,而写锁依赖于独占模式。
简要说明
读写锁的实现,主要依赖于ReentrantReadWriteLock中的内部类:
Sync类是读写锁同步控制的基础,在此基础上增加NofairSync和FairSync来达成公平和非公平的方式获取锁。此外ReadLock和WriteLock两个内部类分别对读锁和写锁进行控制。
获取锁相关源码解析
1. Sync类重写AQS的tryAquire方法尝试获取独占锁
//尝试获取独占锁,用于供给写线程获取锁,只要有另一个线程占有锁,此时不论另一个线程是读线程还是写线程,当前方法都会返回false,因此可以看出此方法是读写和写写互斥的核心
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();//返回站有锁的线程总数
int w = exclusiveCount(c);//返回占有锁的线程中写线程的总数
if (c != 0) {
//c!=0代表此时存在线程占有锁
//下面的if有两种情况:
//1. w等于0,表明现在占有锁的线程都是读线程,直接返回false,读写互斥
//2. w不等于0,则表示此时一定有一个写线程占有锁,也可以间接表示c=w,而此时如果当前尝试获取锁的线程不是当前占有锁的线程,则返回false,写写互斥
// (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");
// 更新state状态,返回true
setState(c + acquires);
return true;
}
if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
写线程只有在锁不被占用或者占用锁的线程就是该线程时才能成功获取到锁。
2. Sync类重写AQS的tryAcquireShared方法获取共享锁
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();//用c表示当前占有锁的线程数
//如果存在写线程占有锁,并且当前线程不是该写线程,则直接返回-1
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表示第一个获取到锁的读线程
//firstReaderHoldCount表示第一个获取到锁的线程占有锁的数量
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;
}
//如果上面if里的代码没有执行,则执行fullTryAcquireShared方法,在该方法中循环尝试获取锁
//fullTryAcquireShared的逻辑比较简单,这里就不作解释了,有需要可以自行去查看源码
return fullTryAcquireShared(current);
}
读线程只要是锁没有没写线程占用,并且占有锁的数量没有超过限定值,则读线程能够成功获取到锁。读线程之间锁的占用时不互斥的。
Sync内部自己定义的tryWriteLock和tryReadLock方法,我们将tryWriteLock方法源码与tryAcquire方法对比,将tryReadLock方法源码与tryAcquireShared方法对比,发现他们流程基本相同,tryWriteLock和tryAcquire方法都是写线程获取锁时会调用到的方法,tryReadLock和tryAquireShared是读线程获取锁时会调用到的方法。
相似与不同:
在WriteLock和ReadLock调用tryLock方法时会调用到tryWriteLock和tryReadLock方法,tryWriteLock和tryReadLock在获取锁的结果直接就是成功或者失败,不会因为失败而阻塞线程,这也是为什么tryLock方法获取锁失败时并不会阻塞线程。
tryAcquire和tryAcquireShared方法是重写AQS中定义的方法,在WriteLock和ReadLock调用lock方法时,会调用到AQS中的acquire方法(由于我在另外两篇AQS和ReentrantLock的文章中已经解释了acquire与tryAcquire、tryAcquireShared方法的关系这里不再重复),acquire方法在获取锁失败后会阻塞线程,这也是为什么lock方法获取锁失败时会阻塞线程的原因。