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,该类在线程池中的作用是非常明显的,所以很有必要单独拿出来分析一番,这对于之后 ...
最新文章
- Android开发学习之路--Notification之初体验
- Linux uptime指令
- 本人对于netty框架的一些理解,怎么与网站上的websock建立连接
- SQL JOIN连接分类[转]
- java做登录时要加锁吗_你用对锁了吗?谈谈 Java “锁” 事
- 每日英语:Targeting Grandpa: China’s Seniors Hunger for Ads
- 搜索树判断 (25 分)(先序建立二叉树)
- Flash | 用几张帧图创建逐帧动画元件并插入场景层的一般步骤
- 不知道如何识别图片文字?这个方法用了都说好
- c语言相反数补码,求一个数的相反数的补码
- 博客优化、收录、RSS技巧
- SpringBoot上传大文件并支持中途取消上传
- 筛质数—(朴素筛法、埃氏筛法、欧拉筛法(线性筛法))
- 公开密钥密码体制(C语言实现RSA加密算法)
- Safari浏览器下载知网文献中文乱码
- 几行代码,把zip文件直接破解
- 提高LCD屏幕刷新效率
- 基于springboot农产品交易平台的设计与实现
- Latex悬挂缩进——整段缩进or列表符号不缩进,段落缩进
- 爱丽丝漫游奇景注释版拿到手了
热门文章
- 智慧停车场行业室外停车场实用价值
- PHP爬取微信公众号文章(可做为扩展类直接使用)
- 深信服AF防火墙(地址转换)服务器映射
- ecshop lbi替换为html,ecshop商城网站首页幻灯片替换成自定义js轮播方法
- 5G2B、物联网平台和边缘计算的通俗理解
- php trip_tags,PHP之字符串函数
- 应讯智能-一体机称重重量获取
- 移动web端安卓手机上元素中的文字垂直不居中的问题
- 建立网络链接编程C语言,用C语言编写一个网络蜘蛛来搜索网上出现的电子邮件地址...
- HTML期末大学生网页设计作业----锤子手机 1页