源码分析:ReentrantReadWriteLock之读写锁
简介
ReentrantReadWriteLock 从字面意思可以看出,是和重入、读写有关系的锁,实际上 ReentrantReadWriteLock 确实也是支持可重入的读写锁,并且支持公平和非公平获取锁两种模式。
为什么会出现读写锁?
普通锁可以保证共享数据在同一时刻只被一个线程访问,就算有多个线程都只是读取的操作,也还是要排队等待获取锁,我们知道数据如果只涉及到读操作,是不会出现线程安全方面的问题的,那这部分加锁是不是可以去掉?或者是加锁不互斥?如果在读多写少的情况下,使用普通的锁,在所有读的情况加锁互斥等待会是一个及其影响系统并发量的问题,如果所有的读操作不互斥,只有涉及到写的时候才互斥,这样会不会大大的提高并发量呢?答案是肯定的,ReentrantReadWriteLock 就是这样干的,读读不互斥,读写、写读、写写都是互斥的,可以大大提高系统并发量。
源码分析
类结构
ReentrantReadWriteLock 仅实现了ReadWriteLock接口
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {...}
ReadWriteLock 接口仅有两个方法,分别是 readLock()
和 writeLock()
;
主要属性
ReentrantReadWriteLock 有3个重要的属性,分别是读锁readerLock,写锁writerLock和同步器sync,源码如下:
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
主要内部类
-
Sync:同步器,继承至AbstractQueuedSynchronizer,定义了两个抽象方法,用于两种模式下自定义实现判断是否要阻塞
abstract static class Sync extends AbstractQueuedSynchronizer{ ... abstract boolean readerShouldBlock(); abstract boolean writerShouldBlock(); ... }
-
NonfairSync:非公平同步器,用于实现非公平锁,继承Sync
static final class NonfairSync extends Sync {...}
-
FairSync:公平同步器,用于实现公平锁,继承Sync
static final class FairSync extends Sync {...}
-
ReadLock:读锁,实现了Lock接口,持有同步器Sync的具体实例
public static class ReadLock implements Lock, java.io.Serializable { ... private final Sync sync; ... }
-
WriteLock:写锁,实现了Lock接口,持有同步器Sync的具体实例
public static class WriteLock implements Lock, java.io.Serializable { ... private final Sync sync; ... }
构造方法
有两个默认的构造方法,无参默认采用非公平锁,有参传入true使用公平锁
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
获取读写锁
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
获取读锁:readLock.lock()
读锁主要是按照共享模式来获取锁的,在前面讲AQS的例子中——基于AQS实现自己的共享锁,也是差不多的流程,只不过不同的锁的实现方法tryAcquireShared有一定的区别。ReentrantReadWriteLock 读锁获取过程源码如下:
public void lock() {
// 共享模式获取锁
sync.acquireShared(1);
}
// acquireShared 是AQS框架里面的代码
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// tryAcquireShared 是RRWLock.Sync 里面的自己实现,所以这里没有公平和非公平所谓之称
protected final int tryAcquireShared(int unused) {
// 当前想要获得锁的线程
Thread current = Thread.currentThread();
// 获取state值
int c = getState();
// 独占锁被占用了,并且不是当前线程占有的,返回-1,出去要排队
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return -1;
// 读锁共享锁的次数
int r = sharedCount(c);
// 判断读是否要阻塞,读共享锁的次数是否超过最大值,CAS 更新锁state值
// readerShouldBlock 的返回要根据同步器是否公平的具体实现来决定
if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
// r==0, 设置第一次获得读锁的读者
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 持有第一个读者读锁的线程重入计数
firstReaderHoldCount++;
} else {
// 除第一个线程之后的其他线程获得读锁
// 每个线程每次获得读锁重入计数+1
// readHolds 就是一个ThreadLocal,里面放的HoldCounter,用来统计每个线程的重入次数
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
// 获得读锁,返回1
return 1;
}
// 上面if分支没进去时,走这里尝试获取读锁
return fullTryAcquireShared(current);
}
上面代码中的readerShouldBlock()
方法有两种情况下会返回true:
-
公平模式下,调用的
AQS.hasQueuedPredecessors()
方法static final class FairSync extends Sync { final boolean writerShouldBlock() { return hasQueuedPredecessors(); } final boolean readerShouldBlock() { return hasQueuedPredecessors(); } } public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; // head 头结点是当前持有锁的节点,它的下一个节点不是当前线程,返回true,表示应该要阻塞当前线程 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }