锁专题(六)这么用心的可重入读写锁讲解,不给作者点个赞吗?( 三 )
从代码中获取读写状态可以看出其是把state(int32位)字段分成高16位与低16位 , 其中高16位表示读锁个数 , 低16位表示写锁个数 , 如下图所示(图来自Java并发编程艺术) 。
文章插图
读写锁状态获取
该图表示当前一个线程获取到了写锁 , 并且重入了两次 , 因此低16位是3 , 并且该线程又获取了读锁 , 并且重入了一次 , 所以高16位是2 , 当写锁被获取时如果读锁不为0那么读锁一定是获取写锁的这个线程 。
读锁的获取读锁的获取主要实现是AQS中的acquireShared方法 , 其调用过程如下代码 。
清单3:读锁获取入口// ReadLockpublic void lock() {sync.acquireShared(1);}// AQSpublic final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)doAcquireShared(arg);}其中doAcquireShared(arg)方法是获取失败之后AQS中入队操作 , 等待被唤醒后重新获取 , 那么关键点就是tryAcquireShared(arg)方法 , 方法有点长 , 因此先总结出获取读锁所经历的步骤 , 获取的第一部分步骤如下:
操作1:读写需要互斥 , 因此当存在写锁并且持有写锁的线程不是该线程时获取失败 。
操作2:是否存在等待写锁的线程 , 存在的话则获取读锁需要等待 , 避免写锁饥饿 。 (写锁优先级是比较高的)
操作3:CAS获取读锁 , 实际上是state字段的高16位自增 。
操作4:获取成功后再ThreadLocal中记录当前线程获取读锁的次数 。
清单4:读锁获取的第一部分protected final int tryAcquireShared(int unused) {Thread current = Thread.currentThread();int c = getState();// 操作1:存在写锁 , 并且写锁不是当前线程则直接去排队if (exclusiveCount(c) != 0int r = sharedCount(c);// 操作2:读锁是否该阻塞 , 对于非公平模式下写锁获取优先级会高 , 如果存在要获取写锁的线程则读锁需要让步 , 公平模式下则先来先到if (!readerShouldBlock()// firstReader是把读锁状态从0变成1的那个线程firstReaderHoldCount = 1;} else if (firstReader == current) {firstReaderHoldCount++;} else {// 这些代码实际上是从ThreadLocal中获取当前线程重入读锁的次数 , 然后自增下 。HoldCounter rh = cachedHoldCounter; // cachedHoldCounter是上一个获取锁成功的线程if (rh == null || rh.tid != getThreadId(current))cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;}return 1;}// 当操作2 , 操作3失败时执行该逻辑return fullTryAcquireShared(current);}当操作2 , 操作3失败时会执行fullTryAcquireShared(current) , 为什么会这样写呢?
个人认为是一种补偿操作 , 操作2与操作3失败并不代表当前线程没有读锁的资格 , 并且这里的读锁是共享锁 , 有资格就应该被获取成功 , 因此给予补偿获取读锁的操作 。
在fullTryAcquireShared(current)中是一个循环获取读锁的过程 , 大致步骤如下:
操作5:等同于操作2 , 存在写锁 , 且写锁线程并非当前线程则直接返回失败
操作6:当前线程是重入读锁 , 这里只会偏向第一个获取读锁的线程以及最后一个获取读锁的线程 , 其他都需要去AQS中排队 。
操作7:CAS改变读锁状态
操作8:同操作4 , 获取成功后再ThreadLocal中记录当前线程获取读锁的次数 。
清单5:读锁获取的第二部分final int fullTryAcquireShared(Thread current) {HoldCounter rh = null;// 最外层嵌套循环for (;;) {int c = getState();// 操作5:存在写锁 , 且写锁并非当前线程则直接返回失败if (exclusiveCount(c) != 0) {if (getExclusiveOwnerThread() != current)return -1;// else we hold the exclusive lock; blocking here// would cause deadlock.// 操作6:如果当前线程是重入读锁则放行} else if (readerShouldBlock()) {// Make sure we're not acquiring read lock reentrantly// 当前是firstReader , 则直接放行,说明是已获取的线程重入读锁if (firstReader == current) {// assert firstReaderHoldCount > 0;} else {// 执行到这里说明是其他线程 , 如果是cachedHoldCounter(其count不为0)也就是上一个获取锁的线程则可以重入 , 否则进入AQS中排队// **这里也是对写锁的让步** , 如果队列中头结点为写锁 , 那么当前获取读锁的线程要进入队列中排队if (rh == null) {rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current)) {rh = readHolds.get();if (rh.count == 0)readHolds.remove();}}// 说明是上述刚初始化的rh , 所以直接去AQS中排队if (rh.count == 0)return -1;}}if (sharedCount(c) == MAX_COUNT)throw new Error("Maximum lock count exceeded");// 操作7:修改读锁状态 , 实际上读锁自增操作if (compareAndSetState(c, c + SHARED_UNIT)) {// 操作8:对ThreadLocal中维护的获取锁次数进行更新 。if (sharedCount(c) == 0) {firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {firstReaderHoldCount++;} else {if (rh == null)rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;cachedHoldCounter = rh; // cache for release}return 1;}}}
- 苹果|iPhone13迎来变化!或回归指纹解锁,这几点备受用户喜爱
- 最新|2020年12月最新购机推荐,这六款各有优点,实用党首选
- 最多|用户最多的中国APP:拼多多第六,淘宝第二,榜首用户超12亿
- 自动任务|赶在三星 S21 发布之前实现语音解锁
- 东六环|250吨“大块头”入井 东六环改造工程将进入盾构阶段
- 合作|六国合作!澜湄水资源合作信息共享平台网站开通
- 线下连锁商|暴涨130%!荷兰、西班牙、比利时百年商超在考拉海购逆势增长
- 长安街|北京 长安街西城段将来单车无法落锁
- 浅谈如何开好一家手机维修店(六):指南舟手机维修培训学校
- 三头六臂钛强悍 映众RTX3060TI显卡首发
