返回顶部
首页 > 资讯 > 精选 >java同步器AQS架构如何释放锁和同步队列
  • 176
分享到

java同步器AQS架构如何释放锁和同步队列

2023-06-29 11:06:49 176人浏览 安东尼
摘要

这篇文章主要为大家展示了“java同步器AQS架构如何释放锁和同步队列”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“java同步器AQS架构如何释放锁和同步队列”这篇文章吧。引导语AQS 的内容

这篇文章主要为大家展示了“java同步器AQS架构如何释放和同步队列”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“java同步器AQS架构如何释放锁和同步队列”这篇文章吧。

    引导语

    AQS 的内容太多,所以我们分成了两个章节,没有看过 AQS 上半章节的同学可以回首看一下哈,上半章节里面说了很多锁的基本概念,基本属性,如何获得锁等等,本章我们主要聊下如何释放锁和同步队列两大部分。

    1、释放锁

    释放锁的触发时机就是我们常用的 Lock.unLock () 方法,目的就是让线程释放对资源的访问权(流程见整体架构图紫色路线)。

    释放锁也是分为两类,一类是排它锁的释放,一类是共享锁的释放,我们分别来看下。

    1.1、释放排它锁 release

    排它锁的释放就比较简单了,从队头开始,找它的下一个节点,如果下一个节点是空的,就会从尾开始,一直找到状态不是取消的节点,然后释放该节点,源码如下:

    // unlock 的基础方法public final boolean release(int arg) {    // tryRelease 交给实现类去实现,一般就是用当前同步器状态减去 arg,如果返回 true 说明成功释放锁。    if (tryRelease(arg)) {        node h = head;        // 头节点不为空,并且非初始化状态        if (h != null && h.waitStatus != 0)            // 从头开始唤醒等待锁的节点            unparkSuccessor(h);        return true;    }    return false;}// 很有意思的方法,当线程释放锁成功后,从 node 开始唤醒同步队列中的节点// 通过唤醒机制,保证线程不会一直在同步队列中阻塞等待private void unparkSuccessor(Node node) {    // node 节点是当前释放锁的节点,也是同步队列的头节点    int ws = node.waitStatus;    // 如果节点已经被取消了,把节点的状态置为初始化    if (ws < 0)        compareAndSetWaitStatus(node, ws, 0);    // 拿出 node 节点的后面一个节点    Node s = node.next;    // s 为空,表示 node 的后一个节点为空    // s.waitStatus 大于0,代表 s 节点已经被取消了    // 遇到以上这两种情况,就从队尾开始,向前遍历,找到第一个 waitStatus 字段不是被取消的    if (s == null || s.waitStatus > 0) {        s = null;        // 这里从尾迭代,而不是从头开始迭代是有原因的。        // 主要是因为节点被阻塞的时候,是在 acquireQueued 方法里面被阻塞的,唤醒时也一定会在 acquireQueued 方法里面被唤醒,唤醒之后的条件是,判断当前节点的前置节点是否是头节点,这里是判断当前节点的前置节点,所以这里必须使用从尾到头的迭代顺序才行,目的就是为了过滤掉无效的前置节点,不然节点被唤醒时,发现其前置节点还是无效节点,就又会陷入阻塞。        for (Node t = tail; t != null && t != node; t = t.prev)            // t.waitStatus <= 0 说明 t 没有被取消,肯定还在等待被唤醒            if (t.waitStatus <= 0)                s = t;    }    // 唤醒以上代码找到的线程    if (s != null)        LockSupport.unpark(s.thread);}

    1.2、释放共享锁 releaseShared

    释放共享锁的方法是 releaseShared,主要分成两步:

    tryReleaseShared 尝试释放当前共享锁,失败返回 false,成功走 2;

    唤醒当前节点的后续阻塞节点,这个方法我们之前看过了,线程在获得共享锁的时候,就会去唤醒其后面的节点,方法名称为:doReleaseShared。

    我们一起来看下 releaseShared 的源码:

    // 共享模式下,释放当前线程的共享锁public final boolean releaseShared(int arg) {    if (tryReleaseShared(arg)) {        // 这个方法就是线程在获得锁时,唤醒后续节点时调用的方法        doReleaseShared();        return true;    }    return false;}

    2、条件队列的重要方法

    在看条件队列的方法之前,我们先得弄明白为什么有了同步队列,还需要条件队列?

    主要是因为并不是所有场景一个同步队列就可以搞定的,在遇到锁 + 队列结合的场景时,就需要 Lock + Condition 配合才行,先使用 Lock 来决定哪些线程可以获得锁,哪些线程需要到同步队列里面排队阻塞;获得锁的多个线程在碰到队列满或者空的时候,可以使用 Condition 来管理这些线程,让这些线程阻塞等待,然后在合适的时机后,被正常唤醒。

    同步队列 + 条件队列联手使用的场景,最多被使用到锁 + 队列的场景中。

    所以说条件队列也是不可或缺的一环。

    接下来我们来看一下条件队列一些比较重要的方法,以下方法都在 ConditionObject 内部类中。

    2.1、入队列等待 await

    // 线程入条件队列public final void await() throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    // 加入到条件队列的队尾    Node node = addConditionWaiter();    // 标记位置 A    // 加入条件队列后,会释放 lock 时申请的资源,唤醒同步队列队列头的节点    // 自己马上就要阻塞了,必须马上释放之前 lock 的资源,不然自己不被唤醒的话,别的线程永远得不到该共享资源了    int savedState = fullyRelease(node);    int interruptMode = 0;    // 确认node不在同步队列上,再阻塞,如果 node 在同步队列上,是不能够上锁的    // 目前想到的只有两种可能:    // 1:node 刚被加入到条件队列中,立马就被其他线程 signal 转移到同步队列中去了    // 2:线程之前在条件队列中沉睡,被唤醒后加入到同步队列中去    while (!isOnSyncQueue(node)) {        // this = AbstractQueuedSynchronizer$ConditionObject        // 阻塞在条件队列上        LockSupport.park(this);        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)            break;    }    // 标记位置 B    // 其他线程通过 signal 已经把 node 从条件队列中转移到同步队列中的数据结构中去了    // 所以这里节点苏醒了,直接尝试 acquireQueued    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)        interruptMode = REINTERRUPT;    if (node.nextWaiter != null) // clean up if cancelled        // 如果状态不是CONDITION,就会自动删除        unlinkCancelledWaiters();    if (interruptMode != 0)        reportInterruptAfterWait(interruptMode);}

    await 方法有几点需要特别注意:

    上述代码标记位置 A 处,节点在准备进入条件队列之前,一定会先释放当前持有的锁,不然自己进去条件队列了,其余的线程都无法获得锁了;上述代码标记位置 B 处,此时节点是被 Condition.signal 或者 signalAll 方法唤醒的,此时节点已经成功的被转移到同步队列中去了(整体架构图中蓝色流程),所以可以直接执行 acquireQueued 方法;Node 在条件队列中的命名,源码喜欢用 Waiter 来命名,所以我们在条件队列中看到 Waiter,其实就是 Node。

    await 方法中有两个重要方法:addConditionWaiter 和 unlinkCancelledWaiters,我们一一看下。

    2.1.1、addConditionWaiter

    addConditionWaiter 方法主要是把节点放到条件队列中,方法源码如下:

    // 增加新的 waiter 到队列中,返回新添加的 waiter// 如果尾节点状态不是 CONDITION 状态,删除条件队列中所有状态不是 CONDITION 的节点// 如果队列为空,新增节点作为队列头节点,否则追加到尾节点上private Node addConditionWaiter() {    Node t = lastWaiter;    // If lastWaiter is cancelled, clean out.    // 如果尾部的 waiter 不是 CONDITION 状态了,删除    if (t != null && t.waitStatus != Node.CONDITION) {        unlinkCancelledWaiters();        t = lastWaiter;    }    // 新建条件队列 node    Node node = new Node(Thread.currentThread(), Node.CONDITION);    // 队列是空的,直接放到队列头    if (t == null)        firstWaiter = node;    // 队列不为空,直接到队列尾部    else        t.nextWaiter = node;    lastWaiter = node;    return node;}

    整体过程比较简单,就是追加到队列的尾部,其中有个重要方法叫做 unlinkCancelledWaiters,这个方法会删除掉条件队列中状态不是 CONDITION 的所有节点,我们来看下 unlinkCancelledWaiters 方法的源码,如下:

    2.1.2、unlinkCancelledWaiters
    // 会检查尾部的 waiter 是不是已经不是CONDITION状态了// 如果不是,删除这些 waiterprivate void unlinkCancelledWaiters() {    Node t = firstWaiter;    // trail 表示上一个状态,这个字段作用非常大,可以把状态都是 CONDITION 的 node 串联起来,即使 node 之间有其他节点都可以    Node trail = null;    while (t != null) {        Node next = t.nextWaiter;        // 当前node的状态不是CONDITION,删除自己        if (t.waitStatus != Node.CONDITION) {            //删除当前node            t.nextWaiter = null;            // 如果 trail 是空的,咱们循环又是从头开始的,说明从头到当前节点的状态都不是 CONDITION            // 都已经被删除了,所以移动队列头节点到当前节点的下一个节点            if (trail == null)                firstWaiter = next;            // 如果找到上次状态是CONDITION的节点的话,先把当前节点删掉,然后把自己挂到上一个状态是 CONDITION 的节点上            else                trail.nextWaiter = next;            // 遍历结束,最后一次找到的CONDITION节点就是尾节点            if (next == null)                lastWaiter = trail;        }        // 状态是 CONDITION 的 Node        else            trail = t;        // 继续循环,循环顺序从头到尾        t = next;    }}

    为了方便大家理解这个方法,画了一个释义图,如下:

    java同步器AQS架构如何释放锁和同步队列

    2.2、单个唤醒 signal 

    signal 方法是唤醒的意思,比如之前队列满了,有了一些线程因为 take 操作而被阻塞进条件队列中,突然队列中的元素被线程 A 消费了,线程 A 就会调用 signal 方法,唤醒之前阻塞的线程,会从条件队列的头节点开始唤醒(流程见整体架构图中蓝色部分),源码如下:

    // 唤醒阻塞在条件队列中的节点public final void signal() {    if (!isHeldExclusively())        throw new IllegalMonitorStateException();    // 从头节点开始唤醒    Node first = firstWaiter;    if (first != null)        // doSignal 方法会把条件队列中的节点转移到同步队列中去        doSignal(first);}// 把条件队列头节点转移到同步队列去private void doSignal(Node first) {    do {        // nextWaiter为空,说明到队尾了        if ( (firstWaiter = first.nextWaiter) == null)            lastWaiter = null;        // 从队列头部开始唤醒,所以直接把头节点.next 置为 null,这种操作其实就是把 node 从条件队列中移除了        // 这里有个重要的点是,每次唤醒都是从队列头部开始唤醒,所以把 next 置为 null 没有关系,如果唤醒是从任意节点开始唤醒的话,就会有问题,容易造成链表的割裂        first.nextWaiter = null;        // transferForSignal 方法会把节点转移到同步队列中去        // 通过 while 保证 transferForSignal 能成功        // 等待队列的 node 不用管他,在 await 的时候,会自动清除状态不是 Condition 的节点(通过 unlinkCancelledWaiters 方法)        // (first = firstWaiter) != null  = true 的话,表示还可以继续循环, = false 说明队列中的元素已经循环完了    } while (!transferForSignal(first) &&             (first = firstWaiter) != null);}

    我们来看下最关键的方法:transferForSignal。

    // 返回 true 表示转移成功, false 失败// 大概思路:// 1. node 追加到同步队列的队尾// 2. 将 node 的前一个节点状态置为 SIGNAL,成功直接返回,失败直接唤醒// 可以看出来 node 的状态此时是 0 了final boolean transferForSignal(Node node) {        // 将 node 的状态从 CONDITION 修改成初始化,失败返回 false    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))        return false;    // 当前队列加入到同步队列,返回的 p 是 node 在同步队列中的前一个节点    // 看命名是 p,实际是 pre 单词的缩写    Node p = enq(node);    int ws = p.waitStatus;    // 状态修改成 SIGNAL,如果成功直接返回    // 把当前节点的前一个节点修改成 SIGNAL 的原因,是因为 SIGNAL 本身就表示当前节点后面的节点都是需要被唤醒的    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))        // 如果 p 节点被取消,或者状态不能修改成SIGNAL,直接唤醒        LockSupport.unpark(node.thread);    return true;}

    整个源码下来,我们可以看到,唤醒条件队列中的节点,实际上就是把条件队列中的节点转移到同步队列中,并把其前置节点状态置为 SIGNAL。

    2.3、全部唤醒 signalAll

    signalAll 的作用是唤醒条件队列中的全部节点,源码如下:

        public final void signalAll() {        if (!isHeldExclusively())            throw new IllegalMonitorStateException();        // 拿到头节点        Node first = firstWaiter;        if (first != null)            // 从头节点开始唤醒条件队列中所有的节点            doSignalAll(first);    }    // 把条件队列所有节点依次转移到同步队列去    private void doSignalAll(Node first) {        lastWaiter = firstWaiter = null;        do {            // 拿出条件队列队列头节点的下一个节点            Node next = first.nextWaiter;            // 把头节点从条件队列中删除            first.nextWaiter = null;            // 头节点转移到同步队列中去            transferForSignal(first);            // 开始循环头节点的下一个节点            first = next;        } while (first != null);    }

    从源码中可以看出,其本质就是 for 循环调用 transferForSignal 方法,将条件队列中的节点循环转移到同步队列中去。

    3、总结

    AQS 源码终于说完了,你都懂了么,可以在默默回忆一下 AQS 架构图,看看这张图现在能不能看懂了。

    java同步器AQS架构如何释放锁和同步队列

    以上是“java同步器AQS架构如何释放锁和同步队列”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网精选频道!

    --结束END--

    本文标题: java同步器AQS架构如何释放锁和同步队列

    本文链接: https://www.lsjlt.com/news/324577.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    猜你喜欢
    • java同步器AQS架构如何释放锁和同步队列
      这篇文章主要为大家展示了“java同步器AQS架构如何释放锁和同步队列”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“java同步器AQS架构如何释放锁和同步队列”这篇文章吧。引导语AQS 的内容...
      99+
      2023-06-29
    • Java中的AQS同步队列问题详解
      目录AQS 同步队列1、AQS 介绍1.1、类图关系1.2、节点剖析2、AQS 实现原理2.1、队列初始化2.2、追加节点3、AQS 唤醒动作AQS 同步队列 1、AQS 介绍 AQ...
      99+
      2024-04-02
    • 什么是AQS抽象队列同步器
      这篇文章主要介绍“什么是AQS抽象队列同步器”,在日常操作中,相信很多人在什么是AQS抽象队列同步器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是AQS抽象队列同步器”...
      99+
      2024-04-02
    • java同步器AQS架构AbstractQueuedSynchronizer原理解析
      目录引导语1、整体架构1.1、类注释1.2、类定义1.3、基本属性1.3.1、简单属性1.3.2 、同步队列属性1.3.3、条件队列的属性1.3.4、Node1.3.5、共享锁和排它...
      99+
      2024-04-02
    • java同步器AQS架构AbstractQueuedSynchronizer原理解析下
      目录引导语1、释放锁1.1、释放排它锁 release1.2、释放共享锁 releaseShared2、条件队列的重要方法2.1、入队列等待 await2.1.1、addCondit...
      99+
      2024-04-02
    • java同步器AQS架构AbstractQueuedSynchronizer原理是什么
      这篇文章主要介绍“java同步器AQS架构AbstractQueuedSynchronizer原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java同步器AQS架构AbstractQueu...
      99+
      2023-06-29
    • Java中的AQS同步队列问题怎么解决
      这篇文章主要介绍“Java中的AQS同步队列问题怎么解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java中的AQS同步队列问题怎么解决”文章能帮助大家解决问题。AQS 同步队列1、AQS 介绍...
      99+
      2023-07-02
    • AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析
      目录前言AQS是什么?用银行办理业务的案例模拟AQS如何进行线程管理和通知机制结语前言 AQS 绝对是JUC的重要基石,也是面试中经常被问到的,所以我们要搞清楚这个AQS到底是什么?...
      99+
      2024-04-02
    • 如何深入理解Java多线程与并发框中的队列同步器AQS
      如何深入理解Java多线程与并发框中的队列同步器AQS,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、 AbstractOwnableSynchronizer 抽象的、可...
      99+
      2023-06-05
    • java并发包JUC同步器框架AQS框架原文翻译
      目录摘要1. 背景介绍2 需求2.1 功能2.2 性能目标3 设计与实现3.1 同步状态3.2 阻塞3.3 队列3.4 条件队列4 用法4.1 公平调度的控制4.2 同步器5 性能5...
      99+
      2024-04-02
    • Java并发编程之JUC并发核心AQS同步队列原理剖析
      目录一、AQS介绍二、AQS中的队列1、同步等待队列2、条件等待队列3、AQS队列节点Node三、同步队列源码分析1、同步队列分析2、同步队列——独占模式源码分析3、同步队列——共享...
      99+
      2024-04-02
    • java并发包JUC同步器框架AQS的示例分析
      小编给大家分享一下java并发包JUC同步器框架AQS的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. 背景介绍通过JCP的JSR166规范,Java...
      99+
      2023-06-29
    • Java框架和Linux:如何实现同步?
      在现代软件开发中,Java框架和Linux操作系统是两个非常重要的组成部分。Java框架提供了强大的编程语言和工具,而Linux则提供了一个稳定可靠的操作系统环境。在许多情况下,这两者的结合可以实现更高效的同步。在本文中,我们将讨论如何在...
      99+
      2023-09-18
      框架 linux 同步
    • python3--线程,锁,同步锁,递归锁,信号量,事件,条件和定时器,队列,线程池
      线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位进程和线程是什么关系?  线程是在进程中的 一个执行单位  多进程 本质上开启的这个进程里就有一个线程  多线程 单纯的在当前进程中开启了多个线...
      99+
      2023-01-30
      递归 线程 信号量
    • ASP和Django框架如何同步使用?
      随着互联网的发展,Web应用程序已成为现代软件开发的主要方式之一。当涉及到Web开发时,ASP和Django是两个最常用的框架之一。但是,ASP和Django之间有什么关系?ASP和Django如何同步使用?本文将深入探讨这个问题。 AS...
      99+
      2023-09-07
      同步 django 框架
    • 如何在Java中使用AbstractQueuedSynchronizer同步框架
      这篇文章将为大家详细讲解有关如何在Java中使用AbstractQueuedSynchronizer同步框架,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。AbstractQueuedSync...
      99+
      2023-05-31
      java abstractqueuedsynchronizer
    • 如何正确的在java中使用同步锁
      这篇文章给大家介绍如何正确的在java中使用同步锁,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。同步锁分类对象锁(this)类锁(类的字节码文件对象即类名.class)字符串锁(比较特别)应用场景在多线程下对共享资源的...
      99+
      2023-05-31
      java 同步锁 ava
    • Flex播放器如何同步显示歌词
      这篇文章将为大家详细讲解有关Flex播放器如何同步显示歌词,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Flex播放器同步显示歌词播放歌曲时要同步显示歌词,首先必须要能解析出lrc格式的歌词文本,之前我还...
      99+
      2023-06-17
    • Git 同步编程算法和 Java:如何同时实现?
      在软件开发中,Git 是一个广泛使用的版本控制工具,而 Java 则是一种常用的编程语言。但是,如何在 Git 中同步编程算法和 Java 代码呢?本文将介绍如何同时实现这两个方面的同步。 一、Git 同步编程算法 在 Git 中同步编程...
      99+
      2023-09-25
      git 同步 编程算法
    • Git 同步编程算法和 Java:如何协同工作?
      在软件开发中,代码的协同工作是必不可少的。Git 是一种流行的版本控制系统,它可以让开发者轻松地同步代码。Java 是一种广泛使用的编程语言,也是许多软件开发项目中的主要语言。本文将介绍如何使用 Git 和 Java 实现协同工作,以及如...
      99+
      2023-09-25
      git 同步 编程算法
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作