以前の記事「Java Concurrency Series [1] ----- AbstractQueuedsynchronizerソースコード分析」では、主にAQSのキューエリアがどのように実装されているか、排他的モードと共有モードとは何か、およびnodesの待機状態を理解する方法について話しています。これらのコンテンツを理解し、習得することは、AQSソースコードをその後読むための鍵です。そのため、読者は最初に私の以前の記事を読んでから、この記事を振り返って理解することをお勧めします。この記事では、ノードが排他的モードで同期キューキューを入力する方法と、同期キューを離れる前に実行される操作を紹介します。 AQSは、排他的なモードと共有モードでロックを取得する3つの方法を提供します。非応答性のスレッド割り込み取得、応答スレッド割り込みの取得、およびタイムアウトの取得を設定します。これら3つの方法の全体的な手順はほぼ同じで、いくつかの異なる部分しかないため、1つの方法を理解して他の方法の実装を見ると、似ています。この記事では、スレッド割り込みに応答しない取得方法に焦点を当て、他の2つの方法についても矛盾について説明します。
1.非応答性のスレッド割り込みでロックを取得する方法は?
//割り込みメソッド取得に応答しません(排他モード)パブリックファイナルボイド獲得(int arg){if(!tryacquire(arg)&& acchirequeued(addwaiter(node.exclusive)、arg)){self interpurte(); }}上記のコードは単純に見えますが、下の図に示す4つのステップを順番に実行します。以下に、段階的に実証および分析します。
ステップ1:!tryacquire(arg)
//ロックを取得してみてください(排他的モード)保護されたブールアッククイア(int arg){新しいunsupportedoperationexception();}この時点で、誰かが来て、彼は最初にドアをノックしようとしました。ドアがロックされていないことがわかった場合(tryacquire(arg)= true)、彼は直接入ります。ドアがロックされていることがわかった場合(tryacquire(arg)= false)、次のステップを実行します。このTryAcquireメソッドは、ロックがいつ開いているか、いつロックが閉じているかを決定します。この方法は、サブクラスによって上書きされ、内部の判断ロジックを書き直す必要があります。
ステップ2:addwaiter(node.exclusive)
//現在のスレッドをノードにラップし、同期キュープライベートノードaddwaiter(ノードモード)のテールに追加します{//ロックノードnode = new node(thread.currentthread()、モード)を保持するモードを指定します。 //同期キューノードpred = tailのテールノードの参照を取得します。 //テールノードが空になっていない場合、同期キューにはすでにノードがあるif(pred!= null){// 1。現在のテールノードnode.prev = predを指します。 // 2。現在のノードをテールノードに設定しますif(compareandsettail(pred、node)){// 3。古いテールノードの後継者を新しいテールノードpred.next = nodeに向けます。ノードを返す; }} //それ以外の場合、それは同期キューがENQ(ノード)初期化されていないことを意味します。 return node;} //ノードEnqueueプライベートノードENQ(最終ノードノード){(;;){//同期キューノードT =テールのテールノードの参照を取得します。 //テールノードが空の場合、(t == null){//同期キューを初期化する場合、同期キューが初期化されていないことを意味します。 }} else {// 1。現在のテールノードnode.prev = t;を指します。 // 2。現在のノードをテールノードに設定しますif(compareandsettail(t、node)){// 3。古いテールノードの後継者を新しいテールノードt.next = nodeに向けます。 tを返します。 }}}}このステップの実行は、ロックの取得が初めて失敗したことを示しているため、その人は自分の番号カードを取得し、キューエリアに入るためにキューエリアに入ります。番号カードを受け取るとき、彼は部屋をどのように占有したいかを宣言します(排他的モードまたは共有モード)。彼はこの時点で座って休んでいなかったことに注意してください(自分自身を掛けてください)。
ステップ3:accirequeued(addwaiter(node.exclusive)、arg)
//ロックを途切れやすい方法で取得する(排他的モード)最終的なブールアックアーキュード(最終ノードノード、int arg){boolean failed = true; try {boolean挿入= false; for(;;){//指定されたノード最終ノードの前のノードの参照を取得しますp = node.pred deverseor(); //現在のノードが同期キューの最初のノードである場合、(p == head && tryacquire(arg)){//指定されたノードをヘッドノードsethead(node)として設定する場合、ロックを取得してみてください。 //ゴミコレクションを支援するには、前のヘッドノードの後継者をクリアしますp.next = null; //成功した取得状態が失敗した= falseを設定します。 //中断された状態を返し、ここでループ全体が実行され、出口リターンが中断されます。 } //それ以外の場合は、ロックステータスがまだ利用できないことを意味します。この時点で、現在のスレッドを吊り下げることができるかどうかを決定します// }}}最後に{//(failed){cancelacquire(node); }}} //現在のノードのプライベート静的ブールを一時停止できるかどうかを判断します。parkafterfailedacquire(node pred、node node){//フォワードノードの待機状態を取得しますint ws = pred.waitstatus; //フォワードノード状態が信号である場合、フォワードノードが現在のノードを起動するため、現在のノードが(ws == node.signal){return true; } if(ws> 0){//次の操作は、同期キューのすべてのキャンセルされたフォワードノードをクリーンアップすることです{node.prev = pred = pred.prev; } while(pred.waitstatus> 0); pred.next = node; } else {//この目的のために、それはフォワードノードの状態が信号ではないことを意味し、0に等しくなる可能性があります。このようにして、フォワードノードは現在のノードを覚醒させません。 } return false;} //現在のスレッドプライベートファイナルブールboolean parkandcheckinterprupt(){locksupport.park(this); return thread.interrupded();}番号記号を取得した後、彼はすぐにこの方法を実装します。ノードが初めてキューエリアに入ると、2つの状況があります。 1つは、彼の前の人が席を離れて部屋に入ったことに気付いたので、彼は座って休むことはなく、子供が終わったかどうかを確認するために再びドアをノックします。中の人がたまたま終了した場合、彼は自分自身を呼ぶことなく急いで行きます。そうでなければ、彼はしばらく座って休むことを検討する必要がありますが、彼はまだ心配していました。彼が座って眠りに落ちた後に誰も彼を思い出させなかったらどうでしょうか?彼は、前から出てきた人がメモを見た後に彼を起こすことができるように、前の男の座席に小さなメモを残しました。別の状況は、彼がキューエリアに入って、彼の前にキーイングしている人が何人かいることを発見したとき、彼はしばらく座ることができるが、その前に、彼はまだ前の人の座席にメモを残していたので(彼はすでに眠っていた)、その人は去る前に彼を起こすことができた。すべてが完了すると、彼は平和に眠ります。 forループ全体には1つの出口のみがあることに注意してください。つまり、スレッドがロックを正常に取得した後にのみ外出できることに注意してください。ロックが取得される前に、forループのparkandcheckinterrupt()メソッドに常に垂れ下がっています。スレッドが目覚めた後、この場所からforループを実行し続けます。
ステップ4:selfinterrupt()
//現在のスレッドは、private static void self utterrupt(){thread.currentthread()。 }上記のスレッド全体がforループのparkandcheckinterrupt()メソッドに吊り下げられているため、正常に取得する前に、スレッド割り込みの形式には応答しません。スレッドがロックを正常に取得し、forループから出てくる場合にのみ、この期間中に誰かがスレッドを中断するように要求するかどうかを確認します。その場合は、自己中間()メソッドを呼び出して、自分自身を掛けてください。
2。スレッド割り込みに応じてロックを取得する方法は?
//中断モードでロックを取得する(排他モード)プライベートボイドdoacquire -interdrightibly(int arg)arturnedexception {//現在のスレッドをノードにラップし、同期キュー最終ノードnode = addwaiter(node.exclusive); boolean failed = true; try {for(;;){// // pがヘッドノードの場合、現在のスレッドは、(p == head && tryacquire(arg)){sethead(node); p.next = null; // gc failed = false; //ロックが正常に取得された後、返品を返します。 } //条件が満たされている場合、現在のスレッドは中断されます。この時点で、割り込みが応答し、例外がスローされます(parkafterfailedacquire(p、node)&& parkandcheckinterrupt()){//スレッドが目覚めた場合、割り込み要求が見つかった場合、障害がスローされます。 new interruptedexception(); }}} finally {if(failed){cancelacquire(node); }}}応答スレッド割り込み方法と非応答性スレッド割り込み方法は、ロックを取得するプロセスでほぼ同じです。唯一の違いは、スレッドがParkandCheckinterruptメソッドから目覚めた後、スレッドが中断されているかどうかを確認することです。もしそうなら、それは中断されたExceptionの例外をスローします。スレッド割り込み取得ロックに応答する代わりに、割り込み要求を受信した後に割り込み状態を設定するだけで、ロックを取得する現在の方法はすぐに終了しません。ノードがロックを正常に取得した後、割り込み状態に基づいて自分自身を掛けるかどうかは決定されません。
3.ロックを取得するためにタイムアウト時間を設定する方法は?
//限られたタイムアウト(排他的モード)でロックを取得するプライベートブールのDoacquirenanos(int、long nanostimeout)は断続的なexceptionをスローします{//現在のシステム時間を長くする= system.nanotime(); //現在のスレッドをノードにラップし、同期キューに追加する最終ノードnode = addwaiter(node.exclusive); boolean failed = true; try {for(;;){// //前のノードがヘッドノードである場合、現在のスレッドは(p == head && tryacquire(arg)){//ヘッドノードsethead(node)を更新する場合、再びロックを取得しようとします。 p.next = null;失敗= false; trueを返します。 } //タイムアウト時間が使用されたら、(nanostimeout <= 0){return false; } //タイムアウト時間がスピンタイムよりも大きい場合、スレッドを一時停止できると判断した後、スレッドは一定期間吊り下げられます(parkafterfailedacquire(p、ノード)&& nanostimeout> spinfortimeoutthhold){// } //システムの現在の時間を長く取得します= System.nanotime(); //タイムアウト時間は、取得ロックnanostimeoutの時間間隔から差し引かれます - = now -lasttime; //最後に再び最後に更新= now; //ロックの取得中に割り込み要求が受信されると、例外がスローされます(thread.interrupded()){new interruptedexception(); }}} finally {if(failed){cancelacquire(node); }}}タイムアウトタイムの取得を設定すると、最初にロックが取得されます。初めての買収が失敗した後、状況に基づいています。着信タイムアウト時間がスピン時間よりも大きい場合、スレッドは一定期間吊り下げられ、それ以外の場合は回転します。ロックが取得されるたびに、ロックを取得するために取られた時間からタイムアウト時間が差し引かれます。タイムアウトが0未満になるまで、タイムアウト時間が使用されていることを意味します。その後、ロックを取得する操作が終了し、取得障害フラグが返されます。タイムアウト時間でロックを取得する過程で、スレッド割り込み要求に応答できることに注意してください。
4.スレッドはロックをどのように解放し、同期キューを残しますか?
//ロック操作のリリース(排他モード)パブリックファイナルブールリリース(int arg){//パスワードロックを回して、(tryrelease(arg)){//ヘッドノードノードh = headを取得します。 //ヘッドノードが空でなく、待機状態が0に等しくない場合、後継ノードを覚ます(h!= null && h.waitstatus!= 0){//後継ノードUnparksuccessess(h); } trueを返します。 } return false;} //後継ノードプライベートボイドunparksuccesser(node node){//指定されたノードint ws = node.waitstatusの待機状態を取得します。 //待機状態を0に更新します(ws <0){compareandsetwaitstatus(node、ws、0); } //指定されたノードnode s = node.nextの後続のノードを取得します。 //後継ノードが空であるか、待機状態がキャンセルされます(s == null || s.waitstatus> 0){s = null; //(ノードt = tail; t!= null && t!= node; t = t.prev){if(t.waitstatus <= 0){s = t; }}} //特定のノードの後に最初のノードを起動します。 }}スレッドが部屋にロックを保持した後、独自のビジネスを行います。作業が完了した後、ロックを解放して部屋を出ます。パスワードロックは、TryReleaseメソッドを介してロック解除できます。 TryReleaseメソッドは、サブクラスによって上書きする必要があることがわかっています。異なるサブクラスの実装ルールは異なります。つまり、異なるサブクラスによって設定されたパスワードは異なります。たとえば、ReentrantLockでは、部屋の人がTryReleaseメソッドを呼び出すたびに、状態が0に縮小されるまで、状態が1倍に削減され、パスワードロックが開きます。このプロセスは、パスワードロックのホイールを常に回しているように見えるかどうかを考えてください。また、ホイールの数は、回転するたびに1削減されます。 CountDownLatchは、これに少し似ていますが、それは1人の人を回しているだけでなく、1人の人を回し、ロックを開くために全員の力を集中させることです。スレッドが部屋を出ると、元のシートが見つかります。つまり、ヘッドノードを見つけます。誰かがシートに小さなメモを残したかどうかを確認してください。ある場合、誰かが眠っていて、それを起こすのを助けるためにそれを頼む必要があることを知っているでしょう、そしてそれはそのスレッドを目覚めさせます。そうでない場合、それは当面の間、誰も同期キューで待っていないことを意味し、誰もそれを目覚めるために必要としないので、それは安心して去ることができます。上記のプロセスは、ロックを排他的モードでリリースするプロセスです。
注:上記の分析はすべてJDK1.7に基づいており、異なるバージョン間に違いがあります。読者は注意を払う必要があります。
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。