AQS详细资源获取流程1. tryAcquire尝试获取资源AQS使用的设计模式是模板方法模式 。
具体代码如下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 发现中断过,则触发中断异常 selfInterrupt();}即AQS抽象基类AbstractQueuedSynchronizer给外部调用时,都是调的acquire(int arg)方法 。这个方法的内容是写死的 。而acquire中 , 需要调用tryAcquire(arg) , 这个方法是需要子类实现的,作用是判断资源是否足够获取arg个
(下面部分代码注释选自: (2条消息) AQS子类的tryAcquire和tryRelease的实现_Mutou_ren的博客-CSDN博客_aqs tryacquire )
ReentrantLock中的tryAcquire实现这里暂时只谈论一种容易理解的tryAcuire实现,其他附加特性的tryAcquire先不提 。
里面主要就做这几件事:
- 获取当前锁的资源数
- 资源数为0 , 说明可以抢,确认是前置节点是头节点,进行CAS试图争抢,抢成功就返回true , 并设置当前线程
- 没抢成功,返回false
- 如果是重入的,则直接set设置增加后的状态值,状态值此时不一定为0和1了
目的是创建一个等待节点Node,并添加到等待队列
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; // 通过CAS竞争队尾 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 竞争队尾失败 , 于是进行CAS频繁循环竞争队尾 enq(node); return node; } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node()))tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }3. acquireQueued循环阻塞-竞争并在 "处于头节点时尝试获取资源->睡眠->唤醒“中循环 。
当已经跑完任务的线程释放资源时,会唤醒之前阻塞的线程 。
当被唤醒后,就会检查自己是不是头节点,如果不是,且认为可以阻塞,那就继续睡觉去了
(下面代码注释部分选自AQS(acquireQueued(Node, int) 3)–队列同步器 - 小窝蜗 - 博客园 (http://cnblogs.com) )
final boolean acquireQueued(final Node node, int arg) { // 标识是否获取资源失败 boolean failed = true; try { // 标识当前线程是否被中断过 boolean interrupted = false; // 自旋操作 for (;;) { // 获取当前节点的前继节点 final Node p = node.predecessor(); // 如果前继节点为头结点,说明排队马上排到自己了,可以尝试获取资源,若获取资源成功,则执行下述操作 if (p == head && tryAcquire(arg)) { // 将当前节点设置为头结点 setHead(node); // 说明前继节点已经释放掉资源了,将其next置空,好让虚拟机提前回收掉前继节点 p.next = null; // help GC // 获取资源成功,修改标记位failed = false; // 返回中断标记 return interrupted; } // 若前继节点不是头结点 , 或者获取资源失败,// 则需要判断是否需要阻塞该节点持有的线程 // 若可以阻塞 , 则继续执行parkAndCheckInterrupt()函数, // 将该线程阻塞直至被唤醒 // 唤醒后会检查是否已经被中断 , 若返回true,则将interrupted标志置于true if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())interrupted = true; } } finally { // 最终获取资源失败,则当前节点放弃获取资源 if (failed) cancelAcquire(node); } }4.shouldParkAfterFailedAcquire 检查是否可以阻塞该方法不会直接阻塞线程 , 因为一旦线程挂起 , 后续就只能通过唤醒机制,中间还发生了内核态用户态切换,消耗很大 。
推荐阅读
- Briefings in Bioinformatics-2021 知识图谱-生物信息学-医学顶刊论文:生物信息学中的图表示学习:趋势、方法和应用
- golang中的nil接收器
- golang中的字符串
- DevOps|从特拉斯辞职风波到研发效能中的不靠谱人干的荒唐事
- 月圆之夜镜中的记忆全成就攻略是什么
- 怎样转发微信内容到朋友圈(如何将微信中的内容转发到朋友圈)
- 4 Java I/O:AIO和NIO中的Selector
- 时空中的绘旅人诸界归一出行服装羽翼获取方法是什么
- 时空中的绘旅人服装的获取方法是什么
- 划拳中的十五、二十怎么玩啊(划拳必赢的十大技巧)