锁专题(六)这么用心的可重入读写锁讲解,不给作者点个赞吗?( 二 )


一般情况下 , 读写锁的性能都会比排它锁要好 , 因为大多数场景读是多于写的 。 在读多于写的情况下 , 读写锁能够提供比排它锁更好的并发性和吞吐量 。
读写锁的优势与传统锁不同的是读写锁的规则是可以共享读 , 但只能一个写 , 总结起来为:读读不互斥 , 读写互斥 , 写写互斥 ,
而一般的独占锁是:读读互斥 , 读写互斥 , 写写互斥 , 而场景中往往读远远大于写 , 读写锁就是为了这种优化而创建出来的一种机制 。
注意是读远远大于写 , 一般情况下独占锁的效率低来源于高并发下对临界区的激烈竞争导致线程上下文切换 。 因此当并发不是很高的情况下 , 读写锁由于需要额外维护读锁的状态 , 可能还不如独占锁的效率高 。 因此需要根据实际情况选择使用 。
jdk 内置实现 ReentrantReadWriteLockjava并发包提供了读写锁的具体实现 ReentrantReadWriteLock , 它主要提供了一下特性:
特性公平性选择:支持公平和非公平(默认)两种获取锁的方式 , 非公平锁的吞吐量优于公平锁;
可重入:支持可重入 , 读线程在获取读锁之后能够再次获取读锁 , 写线程在获取了写锁之后能够再次获取写锁 , 同时也可以获取读锁;
锁降级:线程获取锁的顺序遵循获取写锁 , 获取读锁 , 释放写锁 , 写锁可以降级成为读锁 。
如何保证同步Java中的可重入读写锁ReentrantReadWriteLock是基于AQS(AbstractQueuedSynchronizer)实现的 , 查看源码可以发现内部有一个Sync对象继承自AbstractQueuedSynchronizer , 它用来管理同步机制 , java并发包下的类基本都是用它来提供同步机制的 。
再查看AQS的源码会发现其内部全是native方法及包装这些方法的一些其他方法 。
这些native方法都是调用本地方法 , 利用了运行机器CPU的CAS特性 。
CAS(CompareAndSet)是一种非阻塞算法来保证同步 , 它的效率通常要比加锁算法高很多 , 因为它无阻塞 , 无挂起和恢复 , 无死锁 。
简单来说 , 比较和替换是使用一个期望值和一个变量的当前值进行比较 , 如果当前变量的值与我们期望的值相等 , 就使用一个新值替换当前变量的值 , 返回true , 否则返回false , 线程可以选择继续做其他事情 。
关于CAS可以参考 锁专题(四)深入浅出 CAS 算法 , 乐观锁原来这样实现的 。
如何维护状态ReentrantReadWriteLock内部维护的读写状态是由32位码表示 , 高16位为读状态 , 表示持有读锁的线程数(sharedCount) , 低16位为写状态 , 表示写锁的重入次数 (exclusiveCount) , 状态的改变通过AQS实现 , 保证同步 。
关于ReentrantReadWriteLock的最核心部分大概就是上述两点 , 这里不再细致分析具体代码实现 , 它注重了效率但实现方式不容易我们理解一个读写锁到底该有什么东西 。 因此这里重点通过一个wait/notify版本的读写锁如何实现来深入了解读写锁的原理 。
ReentrantReadWriteLock 源码分析jdk 版本java version "1.8.0_191"接口public class ReentrantReadWriteLockimplements ReadWriteLock, java.io.Serializable 私有属性ReadLock与WriteLock使用的是同一个Sync , 具体怎么实现同一个队列既可以为共享锁 , 又可以表示排他锁下文会具体分析 。
参见 Sync 源码
/** Inner class providing readlock */private final ReentrantReadWriteLock.ReadLock readerLock;/** Inner class providing writelock */private final ReentrantReadWriteLock.WriteLock writerLock;/** Performs all synchronization mechanics */final Sync sync;构造器 /** * Creates a new {@code ReentrantReadWriteLock} with * default (nonfair) ordering properties. */public ReentrantReadWriteLock() {this(false);}/** * Creates a new {@code ReentrantReadWriteLock} with * the given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */public ReentrantReadWriteLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriteLock(this);}Syncsync是读写锁实现的核心 , sync是基于AQS实现的 , 在AQS中核心是state字段和双端队列 , 那么一个一个问题来分析 。
Sync如何同时表示读锁与写锁?

  • 读写锁状态获取
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;/** Returns the number of shared holds represented in count */static int sharedCount(int c) { return c >>> SHARED_SHIFT; }/** Returns the number of exclusive holds represented in count */static int exclusiveCount(int c) { return c}