JUC - ReentrantReadWriteLock 源码分析
简介
ReentrantReadWriteLock,读写锁。维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)。当访问读写比恰当的共享数据时,使用读-写锁所允许的并发性将带来更大的性能提高。
源码分析
ReentrantReadWriteLock的实现方式是在内部定义了一个实现AbstractQueuedSynchronizer(详见:JUC 源码分析 - AbstractQueuedSynchronizer(AQS))的内部类Sync,Sync同时实现了AbstractQueuedSynchronizer中独占模式的获取和释放方法tryAcquire和tryRelease,和共享模式的获取和释放方法tryAcquireShared和tryReleaseShared,写锁WriteLock使用独占模式的方法控制锁状态,读锁ReadLock使用共享模式的方法控制锁状态,在WriteLock和ReadLock中使用同一个AQS的子类Sync,用AQS的status代表读写锁的状态计数,单个int值,通过位运算区分高低位,低16位代表写状态,高16位代表读状态。支持公平非公平实现,支持中断,支持重入,支持锁降级。
当并发读写时:
- 当有线程获取了独占锁,那么后续所有其他线程的独占和共享锁请求会加入同步队列等待,后续当前线程的独占和共享锁可以再次获取;
- 当有线程获取了共享锁,那么后续所有线程的独占锁请求会加入同步队列,后续所有线程的共享锁请求可以继续获取锁;
- 当独占锁完全释放时,会唤醒后继节点,当唤醒的是共享节点时,会传播向后唤醒后继的共享节点;
- 当共享锁完全释放时,且当前没有持有独占锁,会唤醒后继节点,当唤醒的是共享节点时,会传播向后唤醒后继的共享节点;
- 当当前线程已经获取独占锁,那么当前线程可以继续获取共享锁,当独占锁退出时,锁降级为共享锁;
- 一个线程可以同时进入多次共享锁或独占锁;
Sync
abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 6317671515068378041L;static final int SHARED_SHIFT = 16;//32位分高16和低16位static final int SHARED_UNIT = (1 << SHARED_SHIFT);//0000000000000001|0000000000000000static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;//0000000000000000|1111111111111111static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//0000000000000000|1111111111111111static int sharedCount(int c) { return c >>> SHARED_SHIFT; }//获取共享锁计数,高16位static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }//获取独占锁计数,低16位static final class HoldCounter {//每个线程持有的读锁计数。写锁独占所以写锁的低16位就代表独占线程重入计数,读锁共享,所以读锁的高16代表所有线程所有重入次数的计数,HoldCounter用ThreadLocal保存每个线程自己的重入计数。int count = 0;final long tid = Thread.currentThread().getId();}static final class ThreadLocalHoldCounterextends ThreadLocal<HoldCounter> {public HoldCounter initialValue() {//初始化return new HoldCounter();}}private transient ThreadLocalHoldCounter readHolds;//保存每个线程持有的读锁计数,不参与序列化。private transient HoldCounter cachedHoldCounter;//缓存最新获取共享锁的线程的HoldCounterprivate transient Thread firstReader = null;//缓存第一个获取共享锁的线程private transient int firstReaderHoldCount;//缓存第一个获取共享锁的线程的重入计数Sync() {readHolds = new ThreadLocalHoldCounter();setState(getState()); //用volatile的读写保证readHolds的可见性,保证readHolds对所有线程可见。}abstract boolean readerShouldBlock();//共享锁获取是否需要阻塞。控制共享模式的获取锁操作是否公平,子类实现。公平实现会看当前同步队列是否有有效的等待节点,有则返回true,没有则返回false,直接尝试获取锁;非公平实现查看队列中的下一个节点是否是独占模式,是独占模式,则需要阻塞,避免写锁的获取总是发生饥饿,否则,可以直接尝试获取锁。abstract boolean writerShouldBlock();//独占锁获取是否需要阻塞。控制独占模式的获取锁操作是否公平,子类实现。公平实现会看当前同步队列是否有有效的等待节点,有则返回true,需要加入同步队列,顺序获取,没有则返回false,直接尝试获取锁;非公平实现返回false,代表不需要等待可以直接尝试获取锁protected final boolean tryRelease(int releases) {//独占模式的释放锁if (!isHeldExclusively())//如果当前线程不是持有独占锁的线程,将抛出异常throw new IllegalMonitorStateException();int nextc = getState() - releases;boolean free = exclusiveCount(nextc) == 0;//exclusiveCount方法检查释放后的状态的低16位,是0则独占锁完全释放,设置当前持有独占锁的线程位nullif (free)setExclusiveOwnerThread(null);setState(nextc);//因为是独占的释放,所以直接set不会有问题。写volatile,之前的所有内存操作会对所有线程可见,释放之后其他线程才能获取锁。return free;//完全释放独占锁才返回true,这里有可能是锁降级,或者是读写锁完全释放。}protected final boolean tryAcquire(int acquires) {//独占模式的获取锁Thread current = Thread.currentThread();//获取当前线程int c = getState();//获取当前状态int w = exclusiveCount(c);//获取写状态if (c != 0) {//状态不是0,则当前锁已被获取if (w == 0 || current != getExclusiveOwnerThread())//状态不为0,写状态为0,说明读状态不为0。即读锁被获取或者当前线程不是持有独占锁的线程,获取独占锁失败,只有重入的独占锁才能获取return false;if (w + exclusiveCount(acquires) > MAX_COUNT)//写状态超出16位的最大值则失败抛出异常throw new Error("Maximum lock count exceeded");setState(c + acquires);//执行到这里说明状态不为0且写状态不为0,说明独占锁已经被获取,且当前线程是持有独占锁的线程,该获取操作是重入获取独占锁return true;}//当前状态为0if (writerShouldBlock() //写锁是否需要等待,由子类实现,公平实现会看当前同步队列是否有有效的等待节点,有则返回true,需要加入同步队列,顺序获取,没有则返回false,直接尝试获取锁;非公平实现返回false,代表不需要等待可以直接尝试获取锁|| !compareAndSetState(c, c + acquires))//或CAS更新状态失败return false;//不可以直接获取或者CAS更新状态失败,返回falsesetExclusiveOwnerThread(current);return true;//可以获取锁且CAS更新状态成功,设置当前线程为持有独占锁的线程,返回true}protected final boolean tryReleaseShared(int unused) {//共享模式的释放锁Thread current = Thread.currentThread();if (firstReader == current) {if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;} else {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != current.getId())rh = readHolds.get();int count = rh.count;if (count <= 1) {readHolds.remove();if (count <= 0)throw unmatchedUnlockException();}--rh.count;}for (;;) {int c = getState();int nextc = c - SHARED_UNIT;//状态减去1左移16位的数,即读状态减一if (compareAndSetState(c, nextc))//CAS更新读状态成功return nextc == 0;//如果更新后的状态位为0,代表读写锁完全释放,返回true。返回true才会唤醒同步队列中的线程}}private IllegalMonitorStateException unmatchedUnlockException() {return new IllegalMonitorStateException("attempt to unlock read lock, not locked by current thread");}protected final int tryAcquireShared(int unused) {//共享模式的获取锁Thread current = Thread.currentThread();int c = getState();if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)//独占锁被持有,且当前线程不是持有独占锁的线程,返回不可获取return -1;int r = sharedCount(c);//读状态if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {//如果读锁不需要阻塞,且读状态小于最大值,且读状态CAS增加成功,意味着获取共享锁成功if (r == 0) {//如果之前读状态是0,设置firstReader第一个读线程为当前线程,firstReaderHoldCount第一个读线程获取锁计数1firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {//firstReader第一个读线程为当前线程,说明这次是重入的获取共享锁,firstReaderHoldCount第一个读线程获取锁计数+1firstReaderHoldCount++;} else {HoldCounter rh = cachedHoldCounter;//缓存最后一个获取共享锁的线程状态if (rh == null || rh.tid != current.getId())cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;//readHolds保存每个线程持有的读锁计数}return 1;//获取成功}//不可获取return fullTryAcquireShared(current);}final int fullTryAcquireShared(Thread current) {HoldCounter rh = null;for (;;) {int c = getState();if (exclusiveCount(c) != 0) {//独占锁被持有if (getExclusiveOwnerThread() != current)//当前线程不是持有独占锁的线程。返回-1return -1;} else if (readerShouldBlock()) {//如果需要阻塞if (firstReader == current) {} else {if (rh == null) {rh = cachedHoldCounter;if (rh == null || rh.tid != current.getId()) {rh = readHolds.get();if (rh.count == 0)readHolds.remove();}}if (rh.count == 0)return -1;}}if (sharedCount(c) == MAX_COUNT)//共享锁获取已经达到最大计数throw new Error("Maximum lock count exceeded");if (compareAndSetState(c, c + SHARED_UNIT)) {//CAS增加读状态成功if (sharedCount(c) == 0) {//如果之前读状态是0,设置firstReader第一个读线程为当前线程,firstReaderHoldCount第一个读线程获取锁计数1firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {//firstReader第一个读线程为当前线程,说明这次是重入的获取共享锁,firstReaderHoldCount第一个读线程获取锁计数+1firstReaderHoldCount++;} else {if (rh == null)rh = cachedHoldCounter;if (rh == null || rh.tid != current.getId())rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;cachedHoldCounter = rh; // cache for release}return 1;//获取成功}}}final boolean tryWriteLock() {//仅当写入锁在调用期间未被另一个线程保持时获取该锁Thread current = Thread.currentThread();int c = getState();if (c != 0) {//状态不为0int w = exclusiveCount(c);if (w == 0 || current != getExclusiveOwnerThread())//写状态为0或者当前线程不是持有独占锁的线程return false;if (w == MAX_COUNT)throw new Error("Maximum lock count exceeded");}//写状态不为0,且当前线程是持有独占锁的线程if (!compareAndSetState(c, c + 1))return false;setExclusiveOwnerThread(current);return true;//CAS更新成功,设置当前线程为持有独占锁的线程,返回成功获取}final boolean tryReadLock() {//仅当写入锁在调用期间未被另一个线程保持时获取读取锁。Thread current = Thread.currentThread();for (;;) {int c = getState();if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)return false;//这种情况说明独占锁被持有,且不是当前线程持有,一定获取不到共享锁int r = sharedCount(c);if (r == MAX_COUNT)//最大值校验throw new Error("Maximum lock count exceeded");if (compareAndSetState(c, c + SHARED_UNIT)) {//循环CAS直到更新状态成功,获取到共享锁if (r == 0) {firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {firstReaderHoldCount++;} else {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != current.getId())cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;}return true;}}}protected final boolean isHeldExclusively() {//当前线程是否是持有独占锁的线程return getExclusiveOwnerThread() == Thread.currentThread();}// Methods relayed to outer classfinal ConditionObject newCondition() {//独占锁可以获取等待队列return new ConditionObject();}final Thread getOwner() {//获取当前持有独占锁的线程return ((exclusiveCount(getState()) == 0) ?null :getExclusiveOwnerThread());}final int getReadLockCount() {//共享锁计数return sharedCount(getState());}final boolean isWriteLocked() {//独占锁是否被持有return exclusiveCount(getState()) != 0;}final int getWriteHoldCount() {//获取当前线程持有的写锁计数return isHeldExclusively() ? exclusiveCount(getState()) : 0;}final int getReadHoldCount() {//获取当前线程获取共享锁的重入计数if (getReadLockCount() == 0)return 0;Thread current = Thread.currentThread();if (firstReader == current)return firstReaderHoldCount;HoldCounter rh = cachedHoldCounter;if (rh != null && rh.tid == current.getId())return rh.count;int count = readHolds.get().count;if (count == 0) readHolds.remove();return count;}/*** Reconstitute this lock instance from a stream* @param s the stream*/private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();readHolds = new ThreadLocalHoldCounter();setState(0); // reset to unlocked state}final int getCount() { return getState(); }//获取总读写状态}
NonfairSync
static final class NonfairSync extends Sync {private static final long serialVersionUID = -8159625535654395037L;final boolean writerShouldBlock() {return false; // writers can always barge}final boolean readerShouldBlock() {/* As a heuristic to avoid indefinite writer starvation,* block if the thread that momentarily appears to be head* of queue, if one exists, is a waiting writer. This is* only a probabilistic effect since a new reader will not* block if there is a waiting writer behind other enabled* readers that have not yet drained from the queue.*/return apparentlyFirstQueuedIsExclusive();}}final boolean apparentlyFirstQueuedIsExclusive() {Node h, s;return (h = head) != null &&(s = h.next) != null &&!s.isShared() &&s.thread != null;}
FairSync
static final class FairSync extends Sync {private static final long serialVersionUID = -2274990926593161451L;final boolean writerShouldBlock() {return hasQueuedPredecessors();}final boolean readerShouldBlock() {return hasQueuedPredecessors();}}public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}
使用方式
public class ReadWriteLockTest {private final Map<String, String> m = new TreeMap<String, String>();private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();private final Lock r = rwl.readLock();private final Lock w = rwl.writeLock();public String get(String key) {r.lock();try {return m.get(key);} finally {r.unlock();}}public String put(String key, String value) {w.lock();try {return m.put(key, value);} finally {w.unlock();}}public void clear() {w.lock();try {m.clear();} finally {w.unlock();}}}
JUC - ReentrantReadWriteLock 源码分析相关推荐
- 【Java并发编程】16、ReentrantReadWriteLock源码分析
一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...
- Java并发-ReentrantReadWriteLock源码分析
ReentrantLock实现了标准的互斥重入锁,任一时刻只有一个线程能获得锁.考虑这样一个场景:大部分时间都是读操作,写操作很少发生:我们知道,读操作是不会修改共享数据的,如果实现互斥锁,那么即使都 ...
- Java线程池(3)- JUC Executors 源码分析
4.JUC Executors 使用&源码分析 未完待续,写作中- 1.JUC里有几种线程池?各自的使用场景? FixedThreadPool public static ExecutorSe ...
- 读写锁ReentrantReadWriteLock源码分析
文章目录 读写锁的介绍 写锁详解 写锁的获取 写锁的释放 读锁详解 读锁的获取 读锁的释放 锁降级 读写锁的介绍 在并发场景中用于解决线程安全的问题,我们几乎会高频率的使用到独占式锁,通常使用java ...
- 喻红叶《Java并发-ReentrantReadWriteLock源码分析》
ReentrantLock实现了标准的互斥重入锁,任一时刻只有一个线程能获得锁.考虑这样一个场景:大部分时间都是读操作,写操作很少发生:我们知道,读操作是不会修改共享数据的,如果实现互斥锁,那么即使都 ...
- Java多线程 -- JUC包源码分析2 -- Copy On Write/CopyOnWriteArrayList/CopyOnWriteArraySet
本人新书出版,对技术感兴趣的朋友请关注: https://mp.weixin.qq.com/s/uq2cw2Lgf-s4nPHJ4WH4aw 上1篇讲述了Java并发编程的第1个基本思想–CAS/乐观 ...
- JUC AQS ReentrantLock源码分析
Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还 ...
- 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)
一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...
- synchronousqueue场景_【JUC】JDK1.8源码分析之SynchronousQueue(九)
一.前言 本篇是在分析Executors源码时,发现JUC集合框架中的一个重要类没有分析,SynchronousQueue,该类在线程池中的作用是非常明显的,所以很有必要单独拿出来分析一番,这对于之后 ...
最新文章
- PyTorch官方培训教程上线:从基本概念到实操,小白也能上手
- .c/.cpp文件形成.exe文件的过程
- 【SDOI2017】天才黑客
- webservice小坑
- 探秘 Linux 之父 Linus Torvalds 的日常,一不小心就撸出两个创世神作!
- IT服务外包的必要性
- SQL连接MYSQL出现对象名无效_在SQL数据库中创建视图为什么执行时显示对象名无效?...
- 桌面图标全部成被选中状态解决办法
- 知识百科:针式打印机打印头是核心技术
- 资产管理3大重要性,你还不知道吗?
- 不是一番寒彻骨,哪得梅花扑鼻香
- 玩游戏显示计算机丢失,win7玩游戏提示缺少D3DCompiler_47.dll文件怎么处理
- soul_admin之使用zookeeper数据同步
- android 角标最新设置方案
- Node.js 使用expresss,ejs模板引擎实现简单的登录注册
- 【第一性原理】Centos7下编译并行lammps-7Aug19
- FFT从入门到使用(ACM/OI)
- Linux进程管理进程优先级nice
- BFS算法之求单源最短路径
- keysight-34461A使用感受
热门文章
- 分享几个ip定位api
- 【OJ比赛日历】快周末了,不来一场比赛吗? #11.26-12.02 #13场
- 通过异常分数表实现微服务系统根因节点查找(包含代码示例)
- R语言实战读书笔记(四)基本数据管理
- python购物车结算不了_python第八天)——购物车作业优化完成
- 无线抄表系统服务器,无线集中抄表系统设计与实现
- 工厂设备管理难点与解决方案
- .NET Framework 3.5弹出错误代码0x800f0954解决方法
- 计算机个人医院履职总结报告,医院统计个人工作总结范文
- 39个前端精美后台模板(简单实用)