飞利浦·斯塔克|一文弄懂最复杂并发工具类读写锁源码
文章图片
前面几篇文章分析了AQS下实现类的使用 , 今天讲最后一个也是最复杂的一个ReentrantReadWriteLock 。
主要功能ReentrantLock同一时刻只支持一个线程拥有锁 , 但在多数情况下都是对数据的访问而不是修改 , 访问并不会修改数据并不影响其他线程对资源的访问 , 所以不应该是独占的方式拥有资源 , 而是用共享的方式支持对资源并发访问 , 提高系统的吞吐量 。
而ReentrantReadWriteLock读写锁这个类支持多个读锁同时访问 , 当一个线程在持有写锁的时候 , 其他所有线程都不能访问 , 写锁释放后所有线程又能同时访问 。
ReentrantReadWriteLock提供最主要方法readLock()、writeLock()用来返回读锁与写锁 , 返回的是ReentrantReadWriteLock的两个内部类ReadLock、WriteLock分别提供读锁与写锁功能 。
然后还提供了一个些辅助功能比如读队列长度、等待的队列长度、释放写锁持有中 。
主要结构ReentrantReadWriteLock只有三个属性 , 有两个是上面提到的readerLock、writerLock他们是ReentrantReadWriteLock的内部类 , 还有一个属性是Sync继承自AQS 。
readerLock、writerLock提供lock与unlock等方法支持ReentrantReadWriteLock的读锁与写锁功能 , 但是他们所有方法都是直接调用的是sync的方法 , 所以最终还是要看Sync的实现源码 。
Sync源码解析Sync的实现就比之前几个工具类的实现复杂的多了 , 不过目的都是对state的维护 。 不过这里的Sync要实现读锁与写锁 , 这里说下如何在state上实现读与写的标识 。
【飞利浦·斯塔克|一文弄懂最复杂并发工具类读写锁源码】Int型的state是32位 , 把state的前16位作为读锁的统计 , 后16位作为写锁的统计 。
每个线程获取到一个读锁成功也就是readLock().lock()成功就会在state上加216 , 每次释放锁就会减216 , 要统计读锁的数量就对state除以216也就是state右移16位 。
每个线程获取到写锁的时候就在state上加1 , 可重复加锁 , 但是只有state的后16位才是作为writerLock的标记 , 也就是writerLock加锁的次数最多是216-1 , 如果超过了就变成了读锁的标记了 , 要统计写锁的数量就让state&216-1 , 具体源码如下图:
Sync实现了一系列方法来支持这个原理 , 方法详解如下:
tryRelease(int releases):释放独占锁 , 实现简单判断当前线程是不是锁的拥有线程 , 是则state减释放的值 , 否则异常 , 如果释放后的state等于0则更新锁的拥有者为null;
tryAcquire(int acquires):尝试获取独占锁 , 实现流程是
1、判断state的值是否等于0 , 如果等于0说明没有任何锁那么就尝试修改state , 修改成功则获取锁成功否则失败 。
2、如果不等于0 , 如果获取独占锁数量等于0(说明有写锁)或者独占线程不是当前线程则直接返回false , 如果加锁后加锁次数超过最大值则抛出异常 , 否则就只能是当前线程持有独占锁了 , 所以可以更新state , 并返回成功 。
读锁或者说共享锁要稍微复杂点 , 在Sync还有两个类HoldCounter(存储每个线程获取读锁的数量)、ThreadLocalHoldCounter(继承ThreadLocal用来保存HoldCounter) , 利用本地线程类来保存每个线程获取读锁的数量 。
tryAcquireShared(int unused):获取共享锁 , 主要步骤1、判断是否有写锁 , 有则直接返回失败 , 2、没有被写锁持有则判断state的值和加共享锁释放超过数量 , 并尝试更新state , 如果成功则开始修改相关数据 , 3、如果sync的firstReader等于null或者等于当前线程则修改firstReader与firstReaderHoldCount表示锁中sync的第一个读锁以及加锁数量 , 4、如果firstReader有其他线程了 , 那么就要获取本地线程的HoldCounter用来保存加共享锁的次数 。
tryReleaseShared(int unused):释放共享锁 , 如果理顺了加共享锁的实现 , 那么释放锁就简单了 , 1、如果firstReader是当前线程 , 当firstReaderHoldCount等于1的时候则把firstReader置为null , 否则firstReaderHoldCount递减 , 2、如果如果firstReader不是当前线程 , 就获取本地线程的HoldCounter去更新加锁次数 , 同样如果加锁的次数是最后一个则从本地线程中移除HoldCounter 。
总结ReentrantReadWriteLock中的Sync是AQS最全最复杂的实现了 , 它实现了AQS需要实现的四个方法 , 这四个方法对应读写锁的lock、unlock来管理state , 用AQS的模板方法管理线程阻塞与唤醒 。
Sync利用一个int的state就实现了读与写的两个标识值得学习 。 利用本地线程来保存各自线程加读锁的次数也保证了线程的安全 。 还有一些公平非公平锁这类的功能与之前差不多 , 就不再一一赘述了 。
Java程序员日常学习笔记 , 如理解有误欢迎各位交流讨论!
推荐阅读
- 海外网|一文读懂全球疫情:全球累计确诊逾2199万例 日本二季度GDP增速创史上最大跌幅
- iQOO|3998元起!一文看懂iQOO 5/5 Pro规格差异
- 共享|深度透析,一文看懂筑梦之星共享空间的运营逻辑
- 飞利浦|中科蓝讯BT8852蓝牙SoC打入飞利浦供应链,诠释“四双”新标准
- 海外网|一文读懂全球疫情:全球确诊逾2179万例 美疾控中心两名高官辞职
- 幻影布道者|一文读懂匿名以太坊——SERO(超零协议)
- 今年股票型基金前20名中,19只是医药主题基金,一文全对比
- 一文读懂全球疫情:全球确诊逾2156万例 美多位专家学者反思疫情应对问题
- 数据挖掘|一文速览KDD高产华人学者
- 一文了解短视频展示流程及缓存机制