以前の3つの記事の分析を通じて、内部構造と抽象的なシンクロナイザーのいくつかの設計概念を深く理解しています。抽象的なシンクロナイザーは、それぞれ同期キューと条件付きキューである同期状態と2つのキューイング領域を維持することを知っています。公衆トイレを類推として使用しましょう。同期キューはメインキュー領域です。公衆トイレが開いていない場合、トイレに入ろうとするすべての人がここでキューに入らなければなりません。条件キューは、主に条件待つために設定されています。人が最終的にロックを正常に取得し、キューからトイレに入るが、便利な前にトイレットペーパーを持っていないことを発見した場合、想像してみましょう。この状況に遭遇したとき、彼は無力ですが、この事実も受け入れなければなりません。この時点で、最初に外出してトイレットペーパーを準備する必要があります(待機するために条件キューを入力してください)。もちろん、外出する前に、他の人が入ることができるようにロックを解放する必要があります。トイレットペーパー(条件が満たされている)を準備した後、同期キューに戻って再びキューに戻る必要があります。もちろん、部屋に入るすべての人がトイレットペーパーを持っていないわけではありません。操作を中断し、最初に条件キューにキューアップする必要がある他の理由があるかもしれません。したがって、複数の条件キューが存在する可能性があり、異なる待機条件に応じて異なる条件キューが設定されます。条件キューは、一方向リンクリストです。条件インターフェイスは、条件キュー内のすべての操作を定義します。 abstractqueuedsynchronizer内のConditionObjectクラスは、条件インターフェイスを実装します。条件インターフェイスによって定義されている操作を見てみましょう。
パブリックインターフェイス条件{//スレッド中断への応答を待つvoid await()throw strow interrutedexception; //スレッドの中断に応答しないのを待っていますvoid awaitun -interrumdibly(); //相対的な時間を待っている状態の設定//相対的な時間を待っている状態の設定(スピン)ブールン待つ(長い時間、タイムナイトユニット)が中断されたエクセプトをスローします。 //絶対タイムを待機する状態の設定boolean awaituntil(日付の締め切り)が中断されたexceptionをスローします。 //条件キューのヘッドノードvoid信号()を起動します。 //条件のすべてのノードを起動しますqueue queue void signalall(); }条件インターフェイスは非常に多くの方法を定義しますが、合計2つのカテゴリに分けられます。待機から始まる方法は、スレッドが条件キューに入り、待機する方法であり、信号から始まる方法は、条件キューのスレッドを「目覚める」方法です。ここでは、信号メソッドを呼び出すことはスレッドを目覚めさせるかもしれないし、そうしないかもしれないことに注意する必要があります。後で説明するように、スレッドが目覚めたときに状況に依存しますが、信号方式を呼び出すと、スレッドが条件付きキューから同期キューの尾に間違いなく移動します。ナレーションの便利さのために、当面は心配することはありません。信号メソッドを、ウェイクアップ条件付きキュースレッドの動作と呼びます。待機方法には、応答スレッド割り込み待機、非応答スレッド中割り込み待機、相対的な時間の待機、相対的な時間のスピン待機、絶対時間待機の5種類があることに注意してください。つまり、条件キューヘッドノードを覚醒させ、条件キューのすべてのノードを目覚めさせる操作の操作の2種類のみがあります。同じタイプの方法は基本的に同じです。スペースの制限があるため、これらの方法について注意深く話すことは不可能ではありません。 1つの代表的な方法を理解し、それらを理解するために他の方法を調べるだけです。したがって、この記事では、待ち合わせ方法と信号方法について詳細にのみ説明します。他の方法については詳細に説明しませんが、参照用のソースコードを投稿します。
1.スレッド割り込みの状態への応答を待ちます
//スレッドの条件に応じて待機public final void await()interruptedexception {//スレッドが中断されている場合、例外がスローされます} //条件キューノードnode = addConditionWaiter()のテールに現在のスレッドを追加します。 //条件を入力する前にロックを完全にリリースしますint arturtsmode = 0; //スレッドは条件付きでwhile loopで条件付きで待機しています。同期キューの前方ノードはキャンセルされました// 2。同期キューのフォワードノードの状態を信号に故障した状態に設定します// 3。現在のノードは、前方ノードがロックを解放した後に目覚めます。 //現在のスレッドは、すぐに中断されているかどうかを確認します。もしそうなら、それはノードが待機している状態をキャンセルすることを意味します。この時点で、((interruptMode = chemsinterurpthilewhilewaiting(node))!= 0){break; }} //スレッドが目覚めた後、それは排他的モードでロックを取得します} //この操作は、主に信号の前にスレッドが中断するのを防ぐためであり、条件キューから切断されないif(node.nextwaiter!= null){linkcancelledwaiters(); } //割り込みモードに応答する割り込み処理if(arturtsMode!= 0){reportInterrupDafterWait(interrumeMode); }}スレッドが待機方法を呼び出すと、現在のスレッドはノードノードとしてラップされ、条件キューのテールに配置されます。 addConditionWaiterメソッドでは、条件キューエンドノードがキャンセルされていることがわかった場合、nonlinkcancelledwaitersメソッドが呼び出され、条件キューのすべてのキャンセルされたノードをクリアします。このステップは、ノードを挿入するための準備です。テールノードのステータスも条件であることを確認した後、現在のスレッドをラップして条件キューのテールに置くために新しいノードが作成されます。このプロセスは、スレッドを中断せずに同期キューのテールにノードを追加するだけであることに注意してください。
ステップ2:ロックを完全に解放します
//フルリリースロックfinal int fullyrelease(node node){boolean failed = true; try {//現在の同期状態を取得しますint savedState = getState(); //現在の同期状態を使用して、ロックを解放するif(release(savedState)){failed = false; //ロックが正常にリリースされた場合は、savedStateを返します。 } else {//ロックが解放された場合、ランタイム例外をスローします新しいIllegalMonitorStateException(); }}最後に{//ノードがキャンセル状態に設定されていることを確認してください}}}現在のスレッドをノードに包み、条件キューのテールに追加した後、ロックを解放するためにfullyreleaseメソッドが呼び出されます。 Lockはロックを完全に放出するために完全にリリースという名前のメソッドが使用されているため、ロックは条件付き待機前にロックを放す必要があります。そうしないと、ロックを取得できないことに注意してください。ロックが解放された場合、ランタイム例外がスローされます。ロックが正常に放出されると、前の同期状態に戻ります。
ステップ3:条件を待っています
//スレッドはwhile loopで待機していますwhile(!isonsyncqueue(node)){//条件を待っているスレッドはここで吊り下げられています。スレッドが目覚めるいくつかのケースがあります:// 1。同期キューの前方ノードはキャンセルされました// 2。同期キューのフォワードノードの状態を信号に故障した状態に設定します// 3。現在のノードは、前方ノードがロックを解放した後に目覚めます。 locksupport.park(this); //現在のスレッドはすぐに目覚め、中断されているかどうかを確認します。もしそうなら、それはノードが待機している状態をキャンセルすることを意味します。この時点で、((interruptMode = chemsinterurpthilewhilewaiting(node))!= 0){break; }} //スレッド中断の状況を確認する条件がprivate private int checkinterrupthillilewaiting(node node)(//割り込み要求が信号操作の前にあります:throw_ie //割り込み要求は信号操作後:再介入//この期間中に割り込みリクエストは受信されませんでした: (TransferAfterCancelledWait(Node)?Strow_ie:Re -Frinted):0;} //条件キューから同期キューに待機している状態をキャンセルするノードを転送します。 node.condition、0)){//ステータスの変更が成功したら、ノードを同期キューENQ(ノード)のテールに入れます。 trueを返します。 } //これは、CAS操作が失敗したことを示します。これは、(!isonsyncqueue(node)){// sinalメソッドがノードを同期キューに転送していない場合、信号方法の後に割り込みが発生することを示します。 } falseを返します;}上記の2つの操作が完了すると、While Loopに入ります。 whileループが最初にlocksupport.park(this)を呼び出してスレッドを掛けることができるので、スレッドは常にここでブロックされることがわかります。信号メソッドを呼び出した後、ノードを条件付きキューから同期キューに転送するだけです。スレッドが目覚めるかどうかは状況によって異なります。同期キュー内のフォワードノードがノードの転送時にキャンセルされるか、前方ノードの状態が更新されて信号が故障したことがわかった場合、どちらの場合もすぐにスレッドを覚醒させます。それ以外の場合、既に同期キューにあるスレッドは、信号メソッドの終わりには目覚めませんが、前方ノードが目覚めるまで待機します。もちろん、目覚める信号方法を呼び出すことに加えて、スレッドは割り込みに応答することもできます。スレッドがここで割り込み要求を受信した場合、それは引き続き実行されます。スレッドが目覚めた後、割り込みによって目覚められるか、信号方法を介して目覚めているかをすぐに確認することがわかります。割り込みによって目覚めた場合、このノードを同期キューに転送しますが、TransferAfterCancelledWaitメソッドを呼び出すことで達成されます。このステップの最終実行後、割り込みが返され、whileループがジャンプします。
ステップ4:ノードが条件キューから削除された後の操作
//スレッドが目覚めた後、それは排他的なモードでロックを取得します(acchirequeued(node、savedstate)&& arturnmode!= shrow_ie){arturtsmode = retrenturpt;} //この操作は、主に信号の前にスレッドが中断するのを防ぎ、条件をQueueと接触させないif(node.nextwaiter!割り込みモードに応答する処理if(arturtingMode!= 0){ReportInterruptafterWait(arturtyMode);} //待機状態を終了すると、割り込み状況に基づいて対応する処理が行われます。 //割り込みモードが再び途中である場合、それ自体を掛けます} else(arturtsmode == re jutrupt){self interput(); }}スレッドがwhileループ、つまり条件が待機すると、同期キューに戻ります。信号メソッドを呼び戻すためであろうと、スレッドの中断によるものであろうと、ノードは最終的に同期キューになります。現時点では、同期キューでロックを取得する操作を実行するために、ackirequeuedメソッドが呼び出されます。この方法は、排他的モード記事で既に詳しく説明しています。言い換えれば、ノードが条件キューから出てきた後、排他的なモードでロックのセットに従順に行きます。このノードが再びロックを取得した後、この期間中の割り込み状況に基づいてそれに応じて応答するために、Report interpurpterwterwaitメソッドを呼び出します。信号メソッドの前に割り込みが発生した場合、割り込みモードはshrow_ieであり、ロックが再び取得された後に例外がスローされます。信号メソッドの後に割り込みが発生した場合、割り込みモードは再び中断され、ロックが再び取得された後に再び中断されます。
2.割り込みをスレッドするための非応答が待機します
// public final void awaituninterractibly(){//条件キューノードnode = addConditionWaiter()のテールに現在のスレッドを追加する; //フルリリースロックをリリースし、現在の同期状態を返しますint savedState = fullyRelease(node);ブールインタード= false; //ノードはwhile loopで条件付きで待機していますwhile(!isonsyncqueue(node)){//条件キューのすべてのスレッドはここでrocksupport.park(this); //スレッドは目覚め、割り込みがすぐに応答されないことがわかります(thread.interrupded()){furtreded = true; }} if(acchirequeued(node、savedState)||中断){//ここですべての割り込み要求に応答します。スレッドは、状態が待っている間に割り込み要求を受信します// 2。スレッドは、actirequeuedメソッドの中断要求を受信します。 }}3.相対時間の状態を待っている(スピンなし)
//待機状態(相対時間)を設定し、スピンを待っていないパブリックファイナルロングウェイナノス(ロングナノスティムアウト)が中断されますexceptionをスローする{//スレッドが中断された場合、例外がスローされている場合、(thread.interrupted()){throw new interruedexception(); } //条件キューノードnode = addConditionWaiter()のテールに現在のスレッドを追加します。 //完全にリリースしてください。 long lasttime = system.nanotime(); int arturtsmode = 0; while(!isonsyncqueue(node)){//タイムアウトが使い果たされるかどうかを判断します(nanostimeout <= 0l){//タイムアウトが完了した場合、Cancel条件を待機操作TransferAfterCancelledWait(Node)を実行する必要があります。壊す; } //現在のスレッドを一定期間吊るします。この期間中にスレッドが目覚めるか、それ自体で目覚めることがあります。 //スレッドが目覚めた後に最初に割り込み情報を確認します((arturtionMode = checkinterrupthilewhilewaiting(node))!= 0){break; } long now = system.nanotime(); //タイムアウトタイムから条件の待ち時間を引いたnanostimeout- = now -lasttime;最後の= now; } //スレッドが目覚めた後、それは排他的モードでロックを取得します} // TransferAfterCancelledWaitメソッドがNextWaiterを空にしないため、ここでクリーンアップする必要があるすべて(node.nextwaiter!= null){linkcancelledwaiters(); } //割り込みモードに応答する割り込み処理if(arturtsMode!= 0){reportInterrupDafterWait(interrumeMode); } //残りの時間を返しますnanostimeoutを返します - (system.nanotime() - 最後);}4.相対的な時間条件を待っている(スピン)
//待機時間を設定し(相対時間)、スピンを待機してパブリックファイナルブールン待ちawait(Long Time、TimeUnit Unit)を実行します。 } //タイムアウトLONG NANOSTIMEOUT = UNIT.TONANOS(TIME)のミリ秒を取得します。 //スレッドが中断されている場合、例外がスローされます} //条件キューノードnode = addConditionWaiter()のテールに現在のスレッドを追加します。 //条件に入る前にロックを完全にリリースして、int savedState = fullyRelease(node); //現在の時間のミリ秒を取得しますlong lasttime = system.nanotime(); boolean timedout = false; int arturtsmode = 0; while(!isonsyncqueue(node)){//タイムアウトがタイムドアウトの場合、キャンセル条件待機操作を実行する必要があります(nanostimeout <= 0l){timedout = transferaftercancelledwait(node);壊す; } //タイムアウト時間がスピン時間よりも大きい場合、(nanostimeout> = spinfortimeoutthreshold){locksupport.parknanos(this、nanostimeout); } //スレッドが目覚めた後、最初に割り込み情報をチェックします((arturtionMode = checkinterrupthilewhilewaiting(node))!= 0){break; } long now = system.nanotime(); //タイムアウト時間毎回、nanostimeoutを待っている状態の時間を差し引く - = now -lasttime;最後の= now; } //スレッドが目覚めた後、それは排他的モードでロックを取得します} // TransferAfterCancelledWaitメソッドはNextWaiterを空にしないので、ここでクリーンアップする必要があるすべて(node.nextwaiter!= null){linkcancelledwaiters(); } //割り込みモードに応答する割り込み処理if(arturtsMode!= 0){reportInterrupDafterWait(interrumeMode); } //タイムアウトフラグが返されるかどうか!timedout;}5.待機する絶対時間条件を設定します
//時限条件を設定してください(絶対時間)パブリックファイナルブールンawaituntil(日付の締め切り)arturtedexception {if(deadline == null){throw new nullpointerexception(); } //絶対時間のミリ秒を取得しますabstime = deadline.getTime(); //スレッドが中断されている場合、例外がスローされます} //条件キューノードnode = addConditionWaiter()のテールに現在のスレッドを追加します。 //条件を入力する前にロックを完全にリリースしますboolean timedout = false; int arturtsmode = 0; while(!isonsyncqueue(node)){//タイムアウトの場合、キャンセル条件待機操作を実行する必要があります。壊す; } //スレッドを一定期間吊るします。その間、スレッドが目覚められるか、それ自体で目を覚ます時が来るかもしれません。 //スレッドが目覚めた後に最初に割り込み情報を確認します((arturtionMode = checkinterrupthilewhilewaiting(node))!= 0){break; }} //スレッドが目覚めた後、それは排他的モードでロックを取得します} // TransferAfterCancelledWaitメソッドがNextWaiterを空にしないため、ここでクリーンアップする必要があるすべて(node.nextwaiter!= null){linkcancelledwaiters(); } //割り込みモードに応答する割り込み処理if(arturtsMode!= 0){reportInterrupDafterWait(interrumeMode); } //タイムアウトフラグが返されるかどうか!timedout;}6.条件付きキューでヘッドノードを起こす
//条件で次のノードを起動しますqueue public final void signal(){//現在のスレッドがロックを保持しているかどうかを判断します(!isheldexclusively()){新しいIllegalMonitorStateException(); } node first = firstWaiter; //条件のキューにキューアがある場合、キューの場合(first!= null){//条件キュードシグナル(最初)のヘッドノードを覚ます(最初); }} //条件のキュープライベートボイドドシニャール(ノードファースト)のヘッドノードを起動します{do {// 1。 FirstWaiterの参照を1つずつ移動します((FirstWaiter = first.nextwaiter)== null){lastwaiter = null; } // 2。ヘッドノードの後継ノードの参照を空にします。 // 3。ヘッドノードを同期キューに転送すると、転送が完了した後にスレッドを覚ますことができます// 4。 TransferForsignal操作が失敗した場合、次のノードを起動します} while(!transferForsignal(First = FirstWaiter) node.condition、0)){//ステータスを更新する操作が失敗した場合、falseを直接返す// transferAfterCancelledWaitメソッドが最初に状態を変更し、このCAS操作がfalseを失敗させます。 } //このノードを同期キューノードP = ENQ(ノード)のテールに追加します。 int ws = p.waitstatus; if(ws> 0 ||!compareandsetwaitstatus(p、ws、node.signal)){//次の状況が発生すると、現在のスレッドが目覚めます// 1。フォワードノードはキャンセル状態// 2にあります。更新されるフォワードノードの状態は、信号操作に失敗したlocksupport.unpark(node.thread)です。 } trueを返します;}信号法の究極のコアは、転送フォーシグナール法を呼び出すことであることがわかります。 TransferForsignalメソッドでは、最初にCAS操作を使用してノードの状態を条件から0に設定し、ENQメソッドを呼び出して同期キューのテールにノードを追加します。次の判断声明が表示されます。この判断声明は、主にスレッドが目覚められる時期を決定するために使用されます。これらの2つの状況が発生した場合、スレッドはすぐに目覚めます。 1つは、前のノードの状態がキャンセルされ、もう1つは前のノードの状態が更新に失敗したことが発見されたときです。どちらのケースもすぐにスレッドを起動します。そうしないと、条件付きキューから同期キューにノードを転送するだけで実行され、すぐにノードのスレッドを起動しません。 Signalallメソッドはほぼ類似していますが、条件付きキュー内のすべてのノードをループし、同期キューに転送することを除いて。ノードを転送する方法は、依然として転送フォーシグナル法を呼び出します。
7.条件キューのすべてのノードを起動します
//条件の背後にあるすべてのノードを起動しますqueue public final void signalall(){//現在のスレッドがロックを保持しているかどうかを判断します(!isheldexclusively()){throw new IllegalMoniterStateException(); } //条件キューヘッダーノードノードfirst = firstWaiterを取得します。 if(first!= null){//条件のすべてのノードを起動しますdosignalall(first); }} //条件のすべてのノードを覚ますキューキュープライベートボイドdosignalall(node first){//最初にヘッダーノードとテールノードの参照を空にしますlastwaiter = firstwaiter = null; do {//後継ノードの参照を最初に取得= first.nextwaiter; //最初に転送されるノードの後続の参照を空にします。nextwaiter= null; //ノードを条件付きキューから同期キューTransferForsignal(First)に転送します。 //次のノードへの参照をfirst = next; } while(first!= null);}この時点で、私たちの抽象的な陰謀源コード分析全体が終わりました。これらの4つの分析を通じて、誰もがAQをよりよく習得して理解できると思います。このカテゴリは、他の多くの同期カテゴリの基礎であるため、実際に非常に重要です。著者のレベルと表現能力が限られているため、明確な声明や不十分な理解がない場合は、それらを時間内に修正して、一緒に話し合い、学習してください。以下の問題を読むためにメッセージを残すことができます。 AQSコメントソースコードが必要な場合は、著者に連絡してリクエストすることもできます。
注:上記の分析はすべてJDK1.7に基づいており、異なるバージョン間に違いがあります。読者は注意を払う必要があります。
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。