Java并发之AQS源码分析(二)
顶
原
荐
字数 1173
阅读 1
收藏 0
面试:你懂什么是分布式系统吗?Redis分布式锁都不会?>>>
我在 Java并发之AQS源码分析(一) 这篇文章中,从源码的角度深度剖析了 AQS 独占锁模式下的获取锁与释放锁的逻辑,如果你把这部分搞明白了,再看共享锁的实现原理,思路就会清晰很多。下面我们继续从源码中窥探共享锁的实现原理。
共享锁
获取锁
public final void acquireShared(int arg) { // 尝试获取共享锁,小于0表示获取失败 if (tryAcquireShared(arg) < 0) // 执行获取锁失败的逻辑 doAcquireShared(arg); }
这里的 tryAcquireShared 方法是留给实现方去实现获取锁的具体逻辑的,我们主要看 doAcquireShared 方法的实现逻辑:
private void doAcquireShared(int arg) { // 添加共享锁类型节点到队列中 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head) { // 再次尝试获取共享锁 int r = tryAcquireShared(arg); // 如果在这里成功获取共享锁,会进入共享锁唤醒逻辑 if (r >= 0) { // 共享锁唤醒逻辑 setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } // 与独占锁相同的挂起逻辑 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
看到上面的代码,是不是有一种熟悉的感觉, 同样是采用了自旋机制,在线程挂起之前,不断地循环尝试获取锁,不同的是,一旦获取共享锁,会调用 setHeadAndPropagate 方法同时唤醒后继节点,实现共享模式 ,下面是唤醒后继节点代码逻辑:
private void setHeadAndPropagate(Node node, int propagate) { // 头节点 Node h = head; // 设置当前节点为新的头节点 // 这里不需要加锁操作,因为获取共享锁后,会从FIFO队列中依次唤醒队列,并不会产生并发安全问题 setHead(node); if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { // 后继节点 Node s = node.next; // 如果后继节点为空或者后继节点为共享类型,则进行唤醒后继节点 // 这里后继节点为空意思是只剩下当前头节点了 if (s == null || s.isShared()) doReleaseShared(); } }
该方法主要做了两个重要的步骤:
- 将当前节点设置为新的头节点,这点很重要,这意味着当前节点的前置节点(旧头节点)已经获取共享锁了,从队列中去除;
- 调用 doReleaseShared 方法,它会调用 unparkSuccessor 方法唤醒后继节点。
释放锁
public final boolean releaseShared(int arg) { // 由用户自行实现释放锁条件 if (tryReleaseShared(arg)) { // 执行释放锁 doReleaseShared(); return true; } return false; }
下面是释放锁逻辑:
private void doReleaseShared() { for (;;) { // 从头节点开始执行唤醒操作 // 这里需要注意,如果从setHeadAndPropagate方法调用该方法,那么这里的head是新的头节点 Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; //表示后继节点需要被唤醒 if (ws == Node.SIGNAL) { // 初始化节点状态 //这里需要CAS原子操作,因为setHeadAndPropagate和releaseShared这两个方法都会顶用doReleaseShared,避免多次unpark唤醒操作 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 如果初始化节点状态失败,继续循环执行 continue; // loop to recheck cases // 执行唤醒操作 unparkSuccessor(h); } //如果后继节点暂时不需要唤醒,那么当前头节点状态更新为PROPAGATE,确保后续可以传递给后继节点 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } // 如果在唤醒的过程中头节点没有更改,退出循环 // 这里防止其它线程又设置了头节点,说明其它线程获取了共享锁,会继续循环操作 if (h == head) // loop if head changed break; } }
共享锁的释放锁逻辑比独占锁的释放锁逻辑稍微复杂,原因是共享锁需要释放队列中所有共享类型的节点,因此需要循环操作,由于释放锁过程中会涉及多个地方修改节点状态,此时需要 CAS 原子操作来并发安全。
获取共享锁流程图:
总结
更独占锁相比,从流程图也可看出,共享锁的主要特征是当有一个线程获取到锁之后,那么它就会依次唤醒等待队列中可以跟它共享的节点,当然这些节点也是共享锁类型。
© 著作权归作者所有
上一篇: 从源码的角度解析Mybatis的会话机制
下一篇: Java并发之AQS源码分析(一)
相关文章 最新文章
CountDownLatch 相比ReentranceLock,CountDownLatch的流程还是相对比较简单的,CountDownLatch也是基于AQS,它是AQS的共享功能的一个实现。 下面从源代码的实现上详解CountDownLatch。 1、C...
maxam0128
2018/01/23
0
0
java并发编程,内存模型 java 并发编程,volatile内存实现和原理 Java并发编程,并发基础 Java 并发编程,线程池(ThreadPoolExecutor)源码解析 Java并发编程,Executor 框架介绍 Java并发编...
郑加威
2018/12/23
0
0
努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! github:https://github.com/CL0610/Java-concurrency,欢迎题issue和Pull request。所有的文档都是自己亲自码的,如果觉...
你听___
2018/05/06
0
0
JDK 更新速度快的飞起,JDK 12 早期访问构建版已发布,你现在用到了第几版本? 本周Java的最大新闻可能是JDK 11的正式发布。不过在 6 月底,JDK 11 就已经进入了 Rampdown Phase One 阶段,这...
关注公众号_搜云库_每天更新
2018/09/27
0
1
Java多线程系列目录(共43篇) AtomicLongFieldUpdater:通过反射+CAS实现对传入对象的指定long字段实现类似AtomicLong的操作 http://www.cnblogs.com/skywang12345/p/javathreadscategory.ht...
素雷
2017/10/31
0
0
没有更多内容
加载失败,请刷新页面
加载更多最近将 Zipkin 的底层存储切换到了 Elasticsearch,相比 Cassandra,Elasticsearch 拥有更加灵活的查询和聚合方式,所以可以完成一些之前做不到的自定义统计,在此记录一下。 存储结构 Zipk...
xiaomin0322
13分钟前
0
0
目前为止,我们都是从状态流程的开始阶段创建一个状态机,然后一路走下去。但在实际业务中,状态机可能需要在某个环节停留,等待其他业务的触发,然后再继续下面的流程。比如订单,可能在支付...
wphmoon
26分钟前
1
0
ajax是什么 ajax(asynchronous javascript and xml)主要用来实现客户端与服务器端的异步通信,实现页面的局部刷新。 如何创建一个ajax 创建XMLHttpRequest XMLHttpRequest用于在后台与服务...
祖达
32分钟前
1
0
@Componentpublic class SpringBeanUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; public void setApplicationContext(......
小爪进击者
34分钟前
1
0
什么是背包问题 背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的...
AI考拉
40分钟前
3
0
没有更多内容
加载失败,请刷新页面
加载更多以上所述就是小编给大家介绍的《Java并发之AQS源码分析(二) 原 荐》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 并发编程(十)—— Java 并发队列 BlockingQueue 实现之 SynchronousQueue源码分析
- 并发编程(九)—— Java 并发队列 BlockingQueue 实现之 LinkedBlockingQueue 源码分析
- 并发编程(八)—— Java 并发队列 BlockingQueue 实现之 ArrayBlockingQueue 源码分析
- java 并发编程-AQS源码分析
- Java并发之AQS源码分析
- Java 并发编程 -- 线程池源码实战
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
首席产品官1 从新手到行家
车马 / 机械工业出版社 / 2018-9-25 / 79
《首席产品官》共2册,旨在为产品新人成长为产品行家,产品白领成长为产品金领,最后成长为首席产品官(CPO)提供产品认知、能力体系、成长方法三个维度的全方位指导。 作者在互联网领域从业近20年,是中国早期的互联网产品经理,曾是周鸿祎旗下“3721”的产品经理,担任CPO和CEO多年。作者将自己多年来的产品经验体系化,锤炼出了“产品人的能力杠铃模型”(简称“杠铃模型”),简洁、直观、兼容性好、实......一起来看看 《首席产品官1 从新手到行家》 这本书的介绍吧!