本文主要是介绍JUC源码解析-ReentrantReadWriteLock,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
ReentrantLock是独占锁,只允许一个线程执行;CountDownLatch,Semaphore等是共享锁;它们分别利用了AQS的独占与共享功能;那么如果在读操作远多于写操作的情况下该如何选择?读写锁,之前的文章中介绍了如何自己实现一个读写锁,还实现了重入功能,读读,写写,读写,写读四种重入。现在来看看JUC包下的ReentrantReadWriteLock的实现。
先来大致了解下ReentrantReadWriteLock:
- 读锁是个共享锁,写锁是个独占锁。读锁同时被多个线程获取,写锁只能被一个线程获取。读锁与写锁不能同时存在。
- 一个线程可以多次重复获取读锁和写锁
- 锁降级:获取写锁的线程又获取了读锁,之后释放写锁,就变成了一个读锁,这就是锁降级。
- 锁升级:不支持升级。获取读锁的线程去获取写锁的话会造成死锁。
- 重入数:AQS是state,前16位表示读锁的数量,后16位表示写锁的数量
- 公平与非公平两种模式
AQS维护了一个int值,表示同步状态;对于ReentrantLock,state会在0与1之间变化,1表示已被占有后续线程入队列等待,0表示free。对于CountDownLatch,会先将state赋予个大于0的值,在该值变为0后唤醒等待队列中的线程。那么如何用它来即表示读锁又表示写锁呢?读锁我们是允许多个线程同步运行的,我们还允许重入,那么拿什么来记录每个线程读锁的重入数?
针对上面两个问题,对于同步状态status,高16位表示所有线程持有的读锁总数,低16位为一个线程的写锁总数,包括重入。
abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 6317671515068378041L;static final int SHARED_SHIFT = 16;static final int SHARED_UNIT = (1 << SHARED_SHIFT);static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;static int sharedCount(int c) { return c >>> SHARED_SHIFT; }static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
采用ThreadLocal来记录每个线程锁持有的读锁数目。
static final class HoldCounter {int count = 0; // 该线程所持有的读锁数量// Use id, not reference, to avoid garbage retentionfinal long tid = getThreadId(Thread.currentThread());}static final class ThreadLocalHoldCounterextends ThreadLocal<HoldCounter> {public HoldCounter initialValue() {return new HoldCounter();}}private transient ThreadLocalHoldCounter readHolds;private transient HoldCounter cachedHoldCounter;private transient Thread firstReader = null;private transient int firstReaderHoldCount;
- HoldCounter 静态内部类用来记录一个线程的读锁重入数,以及id;
- ThreadLocalHoldCounter继承了ThreadLocal,实现了initialValue方法,作用是在没有set前调用get的话initialValue会被调用,HoldCounter对象会被存储到Entry里,并返回它。变量名为readHolds。因为读锁是共享的,我们利用同步状态的高16位来记录总数,用threadlocal来记录每个线程所持有的读锁数目。对于写锁来说它是独占锁,低16位代表的就是当前线程持有的写锁数目。
- cachedHoldCounter:它是一种优化的手段,为了避免频繁的调用ThreadLocalHoldCounter的读取,更改操作,于是缓存最新一个成功获取锁的线程的HoldCounter,意思是当一个线程需要记录值的时候会先检查自己是否是cachedHoldCounter中缓存的那个线程,是的话就不用再从readHolds中获取了,减少对ThreadLocal的操作。
- firstReader 与firstReaderHoldCount:代表首个获取读锁的线程与其所持有的读锁数,该读锁数不会存储进readHolds,这是种优化,例如获取读锁执行的线程执行然后释放下一个再获取执行释放,这样一个接一个的情况。
同ReentrantLock一样内部实现了非公平与公平两种同步器:NonfairSync &FairSync ,继承自同一同步器Sync。
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();}}static final class FairSync extends Sync {private static final long serialVersionUID = -2274990926593161451L;final boolean writerShouldBlock() {return hasQueuedPredecessors();}final boolean readerShouldBlock() {return hasQueuedPredecessors();}}
只定义了writerShouldBlock & readerShouldBlock两种方法,它们作用在获取锁的过程中,决定当前线程是否该阻塞。
构造器:
public ReentrantRe
这篇关于JUC源码解析-ReentrantReadWriteLock的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!