並行プログラミング| AQSについて話した後、インタビュアーが落ち着かないのはなぜですか?

ここに画像の説明を挿入

AQSとは何か教えてください

AQSは、AbstractQueueSynchronizerの略語であり、ロックやその他の同期コンポーネントの基本的なフレームワークを構築するために使用されます。グローバルなint型の状態変数を定義し、組み込みのFIFO(先入れ先出し)キュー作業を介してリソース競合キューイングを完了します。 。

AQSではどのようなデザインパターンが使用されていますか?

テンプレートデザインモード

シンクロナイザーのステータスを変更してアクセスするにはどうすればよいですか?

  • getState:現在の同期状態を取得します
  • setState:現在の同期状態を設定します
  • compareAndSetState:CASを使用して現在の状態を設定します。このメソッドは、状態設定のアトミック性を保証できます。

AQSはどのようなテンプレートメソッドを提供しますか?

メソッド名 メソッドの説明
Acquire() 排他ロックは同期ステータスを取得します。現在のスレッドが同期ステータスを正常に取得すると、メソッドは戻ります。それ以外の場合は、同期キューに入り、待機し、メソッドは書き換えられたtryAccquire()メソッドを呼び出します。
AcquisitionShared() 共有ロックを取得する現在のスレッドが共有ロックを取得しない場合、同期待機キューに入ります。排他ロックとの違いは、共有ロックを複数のスレッドで同時に保持できることです。
リリース 同期状態を解放し、シンクロナイザーに通知して、待機キューの最初のノードに含まれているスレッドをウェイクアップします。
releaseShare リリース同期

合計7つのテンプレートメソッドが上記にリストされており、排他ロックの取得と解放、共有ロックの取得と解放、クエリと同期ステータスの設定の3つのカテゴリに分類できます。

AQSの同期キューのデータ構造について教えてください。

幸い、事前に面接の質問をスキャンしました。そうしないと、ここで立ち往生してしまいます。まず、写真を撮りましょう、市庁舎に来てください

ここに画像の説明を挿入

  • 現在のスレッドは同期状態の取得に失敗し、シンクロナイザーは現在のスレッドマシンの待機状態とその他の情報をノードノードに構築してキューに追加し、キューの最後に配置します。シンクロナイザーはテールノードをリセットします。
  • キューに参加した後、現在のスレッドはブロックされます
  • 同期状態が解放され、シンクロナイザーが最初のノードをリセットし、シンクロナイザーが待機キューの最初のノードをウェイクアップして、同期状態を再度取得できるようにします。
上記のプロセスでは、注意深い学生が問題を見つける必要があります。ヘッドノードとテールノードを設定するときにスレッドセーフの問題が発生しますか?もしそうなら、私たちは何をすべきか、彼らがそれをどのように行うか見てみましょう

シンクロナイザーは、スレッドセーフを確保するためにテールノードをどのように設定しますか?

最初に分析してみましょう。テールノードを設定するときにスレッドセーフが表示されるのはなぜですか?

複数のスレッドが同時に同期状態(排他ロック)を取得すると、1つのスレッドのみが正常に競合し、他のスレッドは待機キューの最後に配置されます。複数のスレッドが同時にキューの最後にパックされる場合、同時に最後にリソースを奪い合うことに相当し、この時点でスレッドセーフの問題が発生します


キューのテール要素を設定するスレッドセーフを確保するために、シンクロナイザーはcompareAndSetTail(Node expecr、Node update)メソッドを提供します。このメソッドは、現在のスレッドが考えるテールノードと現在設定されているノードを渡します。テールノードは、設定が成功した場合にのみ、現在のノードを前のテールノードに正式に関連付けます。
ここに画像の説明を挿入

シンクロナイザーの最初のノードを設定する方法の場合、スレッドセーフを確保するためにcasを使用する必要がありますか?インタビュアーはひどく笑っていた

その時の最初の反応はもちろんでしたが、脳が速く動いた後、昨夜の面接の質問を思い出したとき、私はこの質問をブラッシングしたようで、彼は言いました:もちろんそうではありません、面接官の笑顔は徐々に消えました、すぐに尋ねます、なぜですか?
必要に応じて最初のエンドノードの同期設定に答えます。複数のスレッドが同時に競合するセットがある場合はテールノードが設定されますが、エンドノードが設定されている場合はスレッドセーフが保証されません。同時にセットアップするために競合する複数のスレッド。

なぜなら、同期状態を解除してヘッドノードを設定する場合、同期状態を取得したスレッドのみを設定でき、排他ロックの同期状態を取得できるためです。もちろん、スレッドは1つしかないため、ここでスレッドの安全性の問題が発生することは不可能です。スレッドの安全性を確保するためにCASを使用する必要はありません。前の最初のノードを切断し、次のノードを最初のノードとして設定するだけです。

ここに画像の説明を挿入

排他同期状態の取得と解放のソースコードを知っていますか?

私の心の中の独り言は、これがすでに私の心の中にあるということです
public final void acquire(int arg) {
    
    
        if(!tryAcquire(arg)&&acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
        seleInterrupt();
        }
}

ははは、コードは数行しかありませんが、同期ロックを取得するプロセス全体が完了します。信じられないでください。お話しします。

  • 現在のスレッドは、カスタムシンクロナイザーによって実装されたtryAcquireメソッドを呼び出します。このメソッドは、スレッドが同期状態を安全に取得できることを保証します。同期状態の取得が失敗した場合、後続のプロセスが実行されます。

  • 排他的な同期ノードを構築し、addWaiterメソッドを呼び出して同期キューの末尾にノードを挿入し、acquireQueuedメソッドを呼び出してスピンし、同期ロックステータスを取得します。そうでない場合は、現在のスレッドをブロックします。

private Node addWaiter(Node node) {
    
    
Node node = new Node(Thread.currentThread(),mode);
// 快速尝试在尾部添加
Node pred = tail;
if(pred!=null){
    
    
node.prev = pred;
if(compareAndSetTail(pred,node)) {
    
    
    pred.next = node;
    return node;
}
}
enq(node);
}

private Node enq(final Node node) {
    
    
for(;;){
    
    
    Node t = tail;
    if(t==null){
    
    
        if(compareAndSetHead(new Node())){
    
    
        tail = node;
        }
    }else{
    
    
    node.prev = t;
    if(compareAndSetTail(t,node)){
    
    
    t.next = node;
    return t;
    }
    }
}
}

上記の2つのコードは、最初に見ると少しめまいがします。LeZaiもそうです。そのとき、上記の分析プロセスと組み合わせると、はるかに明確に感じられます。マインドマップを直接描画します。

ここに画像の説明を挿入

少し休憩して、acquireQueuedメソッドの分析を続けて、そこで何が行われているのかを確認しましょう。

final boolean acquireQueued(final Node node,int arg){
    
    
boolean failed = true;
try{
    
    
    boolean interrupted = false;
    for(;;){
    
    
        final Node p = node.predcessor();
        if(p == head && tryAcquire(arg)) {
    
    
            setHead(node);
            p.next = null;
            failed = false;
            return interrupted;
        }
        }
}
}

上記のコードスニペットを分析すると、acquireQueuedは内部的にスピンを使用して同期状態を取得し、現在のノードの先行ノードがヘッドノードである場合にのみ、tryAcquireを呼び出して同期状態を取得しようとします。それ以外の場合は続行します。スピンする。インタビュアーは、私がここにいることを知ったとき、別の文を追加しました。
**同期ステータスを取得しようとする前に、先行ノードのみがヘッドノードになることができるのはなぜですか。**私は本当に何かを言いたいです、私はこれを言おうとしています!(タフな気性)
ここに画像の説明を挿入

ここに画像の説明を挿入

同期を取得しようとする前に、先行ノードのみがヘッドノードになることができるのはなぜですか

同期状態のスレッドが同期状態を解放すると、後続ノードがウェイクアップされます。FIFOの原則を維持するために、後続のノードがウェイクアップされた後、FIFOの原則を保証するために、その前のノードがヘッドノードであるかどうかを確認する必要があります。排他ロックの解除については説明しません。これは私が言ったことを要約したフローチャートです。
ここに画像の説明を挿入

あなたは排他的ロックと共有ロックについて話していましたが、それらの違いは何ですか?

先生、絵を描いてみましょう。北と北と北北を描いてください。いきなり歌ったので恥ずかしい
ここに画像の説明を挿入

インタビュアーはこう答えました:これはそれらの間の概念的な違いです。コード実装におけるAQSの違いについて話していただけますか?
インタビュアーの回答から判断すると、AQSのソースコードをよく理解しているかどうかを実際にテストしたいと思っているので、話を続けました。

  • 相違点1:共有ロックは複数のスレッドで同時に所有でき、同期ステータスは正常に取得されます。共有ロックは戻り値が0より大きいかどうかで判断され、排他ロックはtrueまたはfalseで判断されます。

  • 違い2:排他ロックが同期状態を解放する場合、1つのスレッドのみが同期状態を解放するため、スレッドセーフを気にする必要はありませんが、共有ロックは同時に複数のスレッドによって所有されるため、スレッドセーフが必要です。同期状態を解放するときに保証されます。通常、CAS +スピンによって実現されます。

面接官は最後の文を逃しました、いつ仕事を始めますか?この瞬間、私の心はこんな感じです
ここに画像の説明を挿入

本日共有したインタビュー内容はこれで終了です。次号でお会いしましょう。

ここに画像の説明を挿入

WeChatでの検索[LeZaiオープントーク]ハンサムな私に従って、[乾物]と返信すると、Javaの基本、Javaの同時実行、マイクロサービスなど、多くのインタビュー資料や建築家の必読の本があなたの選択を待っています。ミドルウェアなど。情報があなたを待っています。
考えずに読む本が多ければ多いほど、あなたは多くのことを知っていると感じるでしょう。そして、読んで考えるほど、あなたはほとんど知らないことがはっきりとわかります。-ヴォルテール

おすすめ

転載: blog.csdn.net/weixin_34311210/article/details/108570273