读写锁的实现原理ReentrantReadWriteLock实现关键点,主要包括:
- 读写状态的设计
- 写锁的获取与释放
- 读锁的获取与释放
- 锁降级
之前谈ReentrantLock的时候,Sync类是继承于AQS,主要以int state为线程锁状态,0表示没有被线程占用,1表示已经有线程占用 。
同样ReentrantReadWriteLock也是继承于AQS来实现同步,那int state怎样同时来区分读锁和写锁的?
如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,ReentrantReadWriteLock将int类型的state将变量切割成两部分:
- 高16位记录读锁状态
- 低16位记录写锁状态
文章插图
abstract static class Sync extends AbstractQueuedSynchronizer {// 版本序列号private static final long serialVersionUID = 6317671515068378041L;// 高16位为读锁,低16位为写锁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;// 本地线程计数器private transient ThreadLocalHoldCounter readHolds;// 缓存的计数器private transient HoldCounter cachedHoldCounter;// 第一个读线程private transient Thread firstReader = null;// 第一个读线程的计数private transient int firstReaderHoldCount;}2.写锁的获取与释放
protected final boolean tryAcquire(int acquires) {/** Walkthrough:* 1. If read count nonzero or write count nonzero*and owner is a different thread, fail.* 2. If count would saturate, fail. (This can only*happen if count is already nonzero.)* 3. Otherwise, this thread is eligible for lock if*it is either a reentrant acquire or*queue policy allows it. If so, update state*and set owner.*/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)//1.如果同步状态不为0,且写状态为0,则表示当前同步状态被读锁获取//2.或者当前拥有写锁的线程不是当前线程if (w == 0 || current != getExclusiveOwnerThread())return false;if (w + exclusiveCount(acquires) > MAX_COUNT)throw new Error("Maximum lock count exceeded");// Reentrant acquiresetState(c + acquires);return true;}if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))return false;setExclusiveOwnerThread(current);return true;}1)c是获取当前锁状态,w是获取写锁的状态 。
2)如果锁状态不为零,而写锁的状态为0,则表示读锁状态不为0,所以当前线程不能获取写锁 。或者锁状态不为零,而写锁的状态也不为0,但是获取写锁的线程不是当前线程,则当前线程不能获取写锁 。
3)写锁是一个可重入的排它锁,在获取同步状态时,增加了一个读锁是否存在的判断 。
写锁的释放与ReentrantLock的释放过程类似,每次释放将写状态减1,直到写状态为0时,才表示该写锁被释放了 。
3.读锁的获取与释放
protected final int tryAcquireShared(int unused) {for(;;) {int c = getState();int nextc = c + (1<<16);if(nextc < c) {throw new Error("Maxumum lock count exceeded");}if(exclusiveCount(c)!=0 && owner != Thread.currentThread())return -1;if(compareAndSetState(c,nextc))return 1;}}1)读锁是一个支持重进入的共享锁,可以被多个线程同时获取 。
2)在没有写状态为0时,读锁总会被成功获取,而所做的也只是增加读状态(线程安全)
3)读状态是所有线程获取读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护 。
读锁的每次释放均减小状态(线程安全的,可能有多个读线程同时释放锁),减小的值是1<<16 。
经验总结扩展阅读
- 怎么取消锁屏的时间
- 为什么阿里Java开发手册不推荐使用Timestamp
- 大华海康NVR录像JAVA下载及WEB播放
- 强人锁男是什么意思?
- 电影宫锁沉香剧情介绍?
- 宫锁沉香是电视剧还是电影?
- 天选cp锁了是什么意思?
- 饭圈锁了是什么意思?
- 锁了是什么意思?
- cp锁了是什么意思?