首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

一文弄懂最复杂并发工具类读写锁源码

前面几篇文章分析了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程序员日常学习笔记,如理解有误欢迎各位交流讨论!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200802A0KPG000?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券