AQS-AbstractQueuedSynchronizer源码解析(二)(下)

简介: AQS-AbstractQueuedSynchronizer源码解析(二)

unparkSuccessor

    private void unparkSuccessor(Node node) {
        /*
         * 如果状态是负数的(即可能需要signal),请尝试清除预期的signal。 如果失败或状态被等待线程更改,则OK。
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        /*
         * 要unpark的线程保留在后继线程中,后者通常就是下一个节点。 但是,如果取消或显然为空,从尾部逆向移动以找到实际的未取消后继者。
         */
        Node s = node.next;
        // 如果下个节点为 null 或者 cancelled,就找到队列最开始的非cancelled 的节点
        if (s == null || s.waitStatus > 0) {
            s = null;
            // 从尾部节点开始到队首方向查找,寻得队列第一个 waitStatus<0 的节点。
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 如果下个节点非空,而且unpark状态<=0的节点
        if (s != null)
            LockSupport.unpark(s.thread);


之前的addWaiter方法的节点入队并不是原子操作


image.png



标识部分可以看做是 tail 入队的原子操作,但是此时pred.next = node;尚未执行,如果这个时候执行了unparkSuccessor,就无法从前往后找了

在产生CANCELLED状态节点的时候,先断开的是 next 指针,prev 指针并未断开,因此也是必须要从后往前遍历才能够遍历完



7.2 releaseShared

以共享模式释放。 如果 tryReleaseShared(int) 返回true,则通过解除一个或多个线程的阻塞来实现。

arg 参数 - 该值传送给 tryReleaseShared(int),但并未实现,可以自定义喜欢的任何内容。


image.png



执行流程

  1. tryReleaseShared 尝试释放共享锁,失败返回 false,true 成功走2
  2. 唤醒当前节点的后续阻塞节点




doReleaseShared

共享模式下的释放动作 - 表示后继信号并确保传播(注意:对于独占模式,如果需要signal,释放仅相当于调用head的unparkSuccessor)。


    private void doReleaseShared() {
        /*
         * 即使有其他正在进行的acquire/release,也要确保 release 传播。 
         * 如果需要signal,则以尝试 unparkSuccessor head节点的常规方式进行。
         * 但如果没有,则将状态设置为 PROPAGATE,以确保释放后继续传播。
         * 此外,在执行此操作时,必须循环以防添加新节点。 
         * 另外,与unparkSuccessor的其他用法不同,我们需要知道CAS重置状态是否失败,如果重新检查,则失败。
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // 循环以重新检查
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // 在失败的CAS上循环
            }
            if (h == head)                   // 如果头结点变了则循环
                break;
        }
    }



8 中断处理

唤醒对应线程后,对应的线程就会继续往下执行。继续执行acquireQueued方法以后,中断如何处理?


8.1 parkAndCheckInterrupt

park 的便捷方法,然后检查是否中断


image.png


再看回 acquireQueued 代码,不论 parkAndCheckInterrupt 返回什么,都会执行下次循环。若此时获取锁成功,就返回当前的 interrupted


image.png



acquireQueued 为True,就会执行 selfInterrupt


image.png


8.2 selfInterrupt


image.png



该方法是为了中断线程。

获取锁后还要中断线程的原因:


当中断线程被唤醒时,并不知道被唤醒的原因,可能是当前线程在等待中被中断,也可能释放锁后被唤醒。因此通过 Thread.interrupted() 检查中断标识并记录,如果发现该线程被中断过,就再中断一次


线程在等待资源的过程中被唤醒,唤醒后还是会不断尝试获取锁,直到抢到锁。即在整个流程中,并不响应中断,只是记录中断的记录。最后抢到锁返回了,那么如果被中断过的话,就需要补充一次中断


目录
相关文章
|
19小时前
|
分布式计算 Java API
Java8 Lambda实现源码解析
Java8的lambda应该大家都比较熟悉了,本文主要从源码层面探讨一下lambda的设计和实现。
|
1天前
|
算法 Java Go
ArrayList源码解析
ArrayList源码解析
7 1
|
1天前
|
存储 安全 Java
【HashMap源码解析(一)(佬你不来看看?)】
【HashMap源码解析(一)(佬你不来看看?)】
6 1
|
8天前
|
缓存 Java 开发者
10个点介绍SpringBoot3工作流程与核心组件源码解析
Spring Boot 是Java开发中100%会使用到的框架,开发者不仅要熟练使用,对其中的核心源码也要了解,正所谓知其然知其所以然,V 哥建议小伙伴们在学习的过程中,一定要去研读一下源码,这有助于你在开发中游刃有余。欢迎一起交流学习心得,一起成长。
|
12天前
|
SQL 缓存 Java
|
12天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
14天前
|
安全 Java Shell
Android13 adb input 调试命令使用和源码解析
Android13 adb input 调试命令使用和源码解析
24 0
Mixtral MOE 部分源码解析
Mixtral MOE 部分源码解析
18 0
|
20天前
|
XML Java 数据格式
Spring IOC的源码解析
【4月更文挑战第17天】Spring IOC(控制反转)的核心功能是通过依赖注入(DI)来管理对象的创建和它们之间的依赖关系。要深入理解Spring IOC的工作原理,我们可以从其源码分析入手,特别是关注如何创建和管理Bean以及依赖注入的实现
19 1
|
20天前
|
机器学习/深度学习 存储 C语言
NumPy源码解析:实现原理探究
【4月更文挑战第17天】本文深入解析NumPy源码,探讨其高效性能背后的实现原理。核心是多维数组`ndarray`,基于同质数据、连续内存分配和形状步幅概念。NumPy利用C语言实现数组管理,通过广播机制允许不同形状数组运算,并借助底层线性代数库实现向量化操作。理解这些机制有助于优化科学计算并应用于其他项目。

推荐镜像

更多