java 读写锁实现原理(Java 读写锁 ReadWriteLock 原理与应用场景详解)
Java并发编程提供了读写锁 ,主要用于读多写少的场景 ,今天我就重点来讲解读写锁的底层实现原理@mikechen
什么是读写锁?
读写锁并不是JAVA所特有的读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁 ,其中读锁允许多个线程同时获得 ,因为读操作本身是线程安全的 ,而写锁则是互斥锁 ,不允许多个线程同时获得写锁 ,并且写操作和读操作也是互斥的 。
所谓的读写锁(Readers-Writer Lock) ,顾名思义就是将一个锁拆分为读锁和写锁两个锁 。
其中读锁允许多个线程同时获得 ,而写锁则是互斥锁 ,不允许多个线程同时获得写锁 ,并且写操作和读操作也是互斥的 。
为什么需要读写锁?
Synchronized 和 ReentrantLock 都是独占锁 ,即在同一时刻只有一个线程获取到锁 。
然而在有些业务场景中,我们大多在读取数据 ,很少写入数据 ,这种情况下,如果仍使用独占锁 ,效率将及其低下 。
针对这种情况 ,Java提供了读写锁——ReentrantReadWriteLock 。
主要解决:对共享资源有读和写的操作 ,且写操作没有读操作那么频繁的场景 。
读写锁的特点
公平性:读写锁支持非公平和公平的锁获取方式 ,非公平锁的吞吐量优于公平锁的吞吐量 ,默认构造的是非公平锁 可重入:在线程获取读锁之后能够再次获取读锁 ,但是不能获取写锁 ,而线程在获取写锁之后能够再次获取写锁 ,同时也能获取读锁 锁降级:线程获取写锁之后获取读锁 ,再释放写锁 ,这样实现了写锁变为读锁 ,也叫锁降级读写锁的使用场景
ReentrantReadWriteLock适合读多写少的场景:
读锁ReentrantReadWriteLock.ReadLock可以被多个线程同时持有, 所以并发能力很高 。
写锁ReentrantReadWriteLock.WriteLock是独占锁, 在一个线程持有写锁时候, 其他线程都不能在抢占, 包含抢占读锁都会阻塞 。
ReentrantReadWriteLock的使用场景总结:其实就是 读读并发 、读写互斥 、写写互斥而已 ,如果一个对象并发读的场景大于并发写的场景,那就可以使用 ReentrantReadWriteLock来达到保证线程安全的前提下提高并发效率 。
读写锁的主要成员和结构图
1. ReentrantReadWriteLock的继承关系
读写锁 ReadWriteLock
读写锁维护了一对相关的锁 ,一个用于只读操作 ,一个用于写入操作 。
只要没有写入,读取锁可以由多个读线程同时保持,写入锁是独占的 。
2.ReentrantReadWriteLock的核心变量ReentrantReadWriteLock类包含三个核心变量:
ReaderLock:读锁,实现了Lock接口 WriterLock:写锁,也实现了Lock接口 Sync:继承自AbstractQueuedSynchronize(AQS),可以为公平锁FairSync 或 非公平锁NonfairSync3.ReentrantReadWriteLock的成员变量和构造函数
读写锁的实现原理
ReentrantReadWriteLock实现关键点 ,主要包括:
读写状态的设计 写锁的获取与释放 读锁的获取与释放 锁降级1.读写状态的设计
之前谈ReentrantLock的时候,Sync类是继承于AQS ,主要以int state为线程锁状态,0表示没有被线程占用 ,1表示已经有线程占用。
同样ReentrantReadWriteLock也是继承于AQS来实现同步 ,那int state怎样同时来区分读锁和写锁的?
如果在一个整型变量上维护多种状态 ,就一定需要“按位切割使用 ”这个变量 ,ReentrantReadWriteLock将int类型的state将变量切割成两部分:
高16位记录读锁状态 低16位记录写锁状态2.写锁的获取与释放
1)c是获取当前锁状态,w是获取写锁的状态 。
2)如果锁状态不为零 ,而写锁的状态为0 ,则表示读锁状态不为0 ,所以当前线程不能获取写锁 。或者锁状态不为零 ,而写锁的状态也不为0,但是获取写锁的线程不是当前线程 ,则当前线程不能获取写锁。
3)写锁是一个可重入的排它锁 ,在获取同步状态时,增加了一个读锁是否存在的判断 。
写锁的释放与ReentrantLock的释放过程类似 ,每次释放将写状态减1 ,直到写状态为0时 ,才表示该写锁被释放了 。
3.读锁的获取与释放
1)读锁是一个支持重进入的共享锁 ,可以被多个线程同时获取 。
2)在没有写状态为0时 ,读锁总会被成功获取 ,而所做的也只是增加读状态(线程安全)
3)读状态是所有线程获取读锁次数的总和 ,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中 ,由线程自身维护 。
读锁的每次释放均减小状态(线程安全的 ,可能有多个读线程同时释放锁) ,减小的值是1<<16 。
4.锁降级
降级是指当前把持住写锁 ,再获取到读锁 ,随后释放(先前拥有的)写锁的过程 。
锁降级过程中的读锁的获取是否有必要,答案是必要的 。主要是为了保证数据的可见性 ,如果当前线程不获取读锁而直接释放写锁 ,假设此刻另一个线程获取的写锁,并修改了数据 ,那么当前线程就步伐感知到线程T的数据更新 ,如果当前线程遵循锁降级的步骤 ,那么线程T将会被阻塞 ,直到当前线程使数据并释放读锁之后 ,线程T才能获取写锁进行数据更新 。
5.读锁与写锁的整体流程
读写锁总结
本篇详细介绍了ReentrantReadWriteLock的特征 、实现 、锁的获取过程 ,通过4个关键点的核心设计:
读写状态的设计 写锁的获取与释放 读锁的获取与释放 锁降级从而才能实现:共享资源有读和写的操作 ,且写操作没有读操作那么频繁的应用场景 。
作者简介
陈睿|mikechen,10年+大厂架构经验,《BAT架构技术500期》系列文章作者 ,专注于互联网架构技术 。
阅读mikechen的互联网架构更多技术文章合集
Java并发|JVM|MySQL|Spring|Redis|分布式|高并发
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!