目次
はじめに
1. ロックを解放します
1.1. 排他ロックの解放 解放
1.2. 共有ロックの解放 releaseShared
2. 条件付きキューの重要なメソッド
2.1. キューに入って await を待ちます
2.1.1, addConditionWaiter
2.1.2, unlinkCancelledWaiters
ホームページ Java &#&チュートリアル Java シンクロナイザー AQS アーキテクチャはどのようにしてロックを解放し、キューを同期しますか

Java シンクロナイザー AQS アーキテクチャはどのようにしてロックを解放し、キューを同期しますか

May 11, 2023 pm 05:16 PM
java aqs

    はじめに

    AQS は内容が多すぎるため、前半を読んでいない学生も振り返ることができるように 2 つの章に分けました。この章の前半では、ロックの基本的な概念、基本的なプロパティ、ロックの取得方法などについて説明します。この章では、主にロックと同期キューを解放する方法について説明します。

    1. ロックを解放します

    ロックの解放のトリガー時間は、一般的に使用される Lock.unLock () メソッドであり、その目的は、スレッドがリソースへのアクセスを解放できるようにすることです (紫色の部分を参照)プロセスの全体的なアーキテクチャ図のルート)。

    ロックの解除も排他ロックの解除と共有ロックの解除の2つに分かれますので、分けて見てみましょう。

    1.1. 排他ロックの解放 解放

    排他ロックの解放は比較的簡単です. キューの先頭から開始して次のノードを見つけます. 次のノードが空の場合は,末尾から開始してステータスが解除されていないノードを見つけてノードを解放します ソースコードは次のとおりです:

    // 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

    The共有ロックを解放するメソッドは releaseShared で、これは主に 2 つのステップに分かれています:

    tryReleaseShared は現在の共有ロックを解放しようとします。失敗した場合は false を返します。成功した場合は 2 に進みます。

    現在のノードの後続のブロッキング ノードをウェイクアップします。このメソッドは前にも見たことがあります。スレッドは共有を取得します。ロックすると、その背後にあるノードをウェイクアップします。メソッド名は doReleaseShared です。

    releaseShared のソース コードを見てみましょう:

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

    2. 条件付きキューの重要なメソッド

    条件付きキューのメソッドを見る前に、まず理解する必要があります。同期キューがある場合でも、条件付きキューが必要ですか?

    主な理由は、すべてのシナリオが同期キューで処理できるわけではないためです。ロック キューが結合されるシナリオが発生した場合は、ロック条件が必要です。まず、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 メソッドを直接実行できます。ノードはソースコードは名前に Waiter を使用することを好むため、条件キューで Waiter が実際には Node であることがわかります。

    await メソッドには、addConditionWaiter と unlinkCancelledWaiters という 2 つの重要なメソッドがあります。1 つずつ見てみましょう。

    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状态了
    // 如果不是,删除这些 waiter
    private 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;
        }
    }
    ログイン後にコピー

    このメソッドを誰でも理解しやすいように、次のような解釈図を描きました。

    2.2. 単一ウェイクアップ信号 Java シンクロナイザー AQS アーキテクチャはどのようにしてロックを解放し、キューを同期しますか

    信号方式はウェイクアップです たとえば、以前キューがいっぱいで、一部のスレッドがテイクのために条件付きキューでブロックされたことを意味します。突然、キュー内の要素がスレッド A によって消費されました。スレッド A は、シグナル メソッドを呼び出して、以前にブロックされていたスレッドをウェイクアップし、スレッドが開始されます。条件付きキューのヘッド ノードがウェイクアップし始めます (「プロセスの全体的なアーキテクチャ図の青い部分) ソース コードは次のとおりです:

    // 唤醒阻塞在条件队列中的节点
    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) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        // 将 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 の機能は、条件キュー内のすべてのノードをウェイクアップすることです。ソース コードは次のとおりです。ソースコードから見ると、その本質は次のとおりです。 transferForSignal メソッドがループ内で呼び出され、条件キュー内のノードを同期キューに転送します。

    3. 概要

    AQS ソース コードがついに完成しました。理解できましたか? AQS アーキテクチャ図を黙って思い出して、理解できるかどうかを確認してください。

    Java シンクロナイザー AQS アーキテクチャはどのようにしてロックを解放し、キューを同期しますか

    以上がJava シンクロナイザー AQS アーキテクチャはどのようにしてロックを解放し、キューを同期しますかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

    ホットAIツール

    Undresser.AI Undress

    Undresser.AI Undress

    リアルなヌード写真を作成する AI 搭載アプリ

    AI Clothes Remover

    AI Clothes Remover

    写真から衣服を削除するオンライン AI ツール。

    Undress AI Tool

    Undress AI Tool

    脱衣画像を無料で

    Clothoff.io

    Clothoff.io

    AI衣類リムーバー

    AI Hentai Generator

    AI Hentai Generator

    AIヘンタイを無料で生成します。

    ホットツール

    メモ帳++7.3.1

    メモ帳++7.3.1

    使いやすく無料のコードエディター

    SublimeText3 中国語版

    SublimeText3 中国語版

    中国語版、とても使いやすい

    ゼンドスタジオ 13.0.1

    ゼンドスタジオ 13.0.1

    強力な PHP 統合開発環境

    ドリームウィーバー CS6

    ドリームウィーバー CS6

    ビジュアル Web 開発ツール

    SublimeText3 Mac版

    SublimeText3 Mac版

    神レベルのコード編集ソフト(SublimeText3)

    Javaの平方根 Javaの平方根 Aug 30, 2024 pm 04:26 PM

    Java の平方根のガイド。ここでは、Java で平方根がどのように機能するかを、例とそのコード実装をそれぞれ示して説明します。

    Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

    Java における完全数のガイド。ここでは、定義、Java で完全数を確認する方法、コード実装の例について説明します。

    Javaのアームストロング数 Javaのアームストロング数 Aug 30, 2024 pm 04:26 PM

    Java のアームストロング番号に関するガイド。ここでは、Java でのアームストロング数の概要とコードの一部について説明します。

    Java の乱数ジェネレーター Java の乱数ジェネレーター Aug 30, 2024 pm 04:27 PM

    Java の乱数ジェネレーターのガイド。ここでは、Java の関数について例を挙げて説明し、2 つの異なるジェネレーターについて例を挙げて説明します。

    ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

    Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

    Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

    Java のスミス番号のガイド。ここでは定義、Java でスミス番号を確認する方法について説明します。コード実装の例。

    Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

    この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

    Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

    Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

    See all articles