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: