Java 5.0の前に、共有オブジェクトへのアクセスを調整するために使用できる唯一のメカニズムは、同期して揮発性でした。同期されたキーワードは組み込みのロックを実装し、揮発性キーワードがマルチスレッドのメモリの可視性を保証することを知っています。ほとんどの場合、これらのメカニズムは仕事をうまく行うことができますが、ロックの取得を待っているスレッドを中断できない、時間制限ロックメカニズムを実装できない、非ブロッキング構造のロックルールなどを実装できないなど、これ以上の高度な機能を実装することはできません。したがって、Java 5.0:ReentrantLockに新しいメカニズムが追加されています。 ReentrantLockクラスはロックインターフェイスを実装し、同期したのと同じミューテックスとメモリの可視性を提供します。その基礎となる層は、AQを介してマルチスレッドの同期を実現することです。内蔵ロックと比較して、ReentrantLockはより豊富なロックメカニズムを提供するだけでなく、パフォーマンスの内蔵ロックよりも劣ることはありません(以前のバージョンの内蔵ロックよりも優れています)。 ReentrantLockの非常に多くの利点について話しているので、そのソースコードを明らかにして、その特定の実装を見てみましょう。
1。同期されたキーワードの概要
Javaは、マルチスレッドの同期をサポートするための組み込みロックを提供します。 JVMは、同期されたキーワードに従って同期されたコードブロックを識別します。スレッドが同期コードブロックに入ると、ロックが自動的に取得されます。同期コードブロックを終了すると、ロックは自動的に解放されます。 1つのスレッドがロックを取得すると、他のスレッドがブロックされます。各Javaオブジェクトは、同期を実装するロックとして使用できます。同期されたキーワードを使用して、オブジェクトメソッド、静的メソッド、コードブロックを変更できます。オブジェクトメソッドと静的メソッドを変更する場合、ロックはメソッドが配置されているオブジェクトとクラスオブジェクトです。コードブロックを変更するときは、追加のオブジェクトをロックとして提供する必要があります。各Javaオブジェクトをロックとして使用できる理由は、モニターオブジェクト(操作)がオブジェクトヘッダーに関連付けられているためです。スレッドが同期コードブロックを入力すると、モニターオブジェクトが自動的に保持され、終了するとモニターオブジェクトが自動的にリリースされます。モニターオブジェクトが保持されると、他のスレッドがブロックされます。もちろん、これらの同期操作はJVM基礎層によって実装されていますが、同期されたキーワード変更方法とコードブロックの基礎となる実装にはまだいくつかの違いがあります。同期されたキーワード変更方法は、暗黙的に同期されています。つまり、バイトコード命令を介して制御する必要はありません。 JVMは、メソッドテーブルのACC_SynChronized Accessフラグに基づいたメソッドが同期されたメソッドであるかどうかを区別できます。同期されたキーワードによって変更されたコードブロックは明示的に同期されており、モニターエンターとモニトレシットのバイトコード命令を介したパイプラインのスレッドの保持とリリースを制御します。モニターオブジェクトは、_Countフィールドを内部に保持します。 _ count等しい0は、パイプラインが保持されていないことを意味し、_count 0を超えると、パイプラインが保持されていることを意味します。保持スレッドが再入力されるたびに、_Countが追加され、保持スレッドが終了するたびに_Countが1に削減されます。これは、組み込みロック再入力の実装原則です。さらに、モニターオブジェクト_entryListと_waitsetには、同期キューとAQSの条件付きキューに対応する2つのキューがあります。スレッドがロックの取得に失敗すると、_entryListでブロックされます。ロックオブジェクトの待機方法が呼び出されると、スレッドは_waitsetを入力して待機します。これは、スレッドの同期と組み込みロックを待機する条件付きの実装原則です。
2。ReentrantLockと同期の比較
同期されたキーワードは、Javaが提供する組み込みロックメカニズムです。同期操作は、基礎となるJVMによって実装されます。 ReentrantLockは、java.util.concurrentパッケージによって提供される明示的なロックであり、その同期操作はAQSシンクロナイザーによって駆動されます。 ReentrantLockは、組み込みロックと同じセマンティクスをロックとメモリに提供します。さらに、時限ロック待機、割り込み可能なロック待機、公正なロック、非ブロック構造ロックの実装など、他の機能を提供します。さらに、ReentrantLockは、初期のJDKバージョンで特定のパフォーマンスの利点もありました。 ReentrantLockには非常に多くの利点があるため、なぜ同期したキーワードを使用する必要があるのですか?実際、多くの人がReentrantLockを使用して、同期されたキーワードのロック操作を置き換えています。ただし、組み込みのロックにはまだ独自の利点があります。組み込みのロックは多くの開発者に馴染みがあり、よりシンプルでコンパクトで使用されています。明示的なロックは、最終的なブロックで手動でロック解除と呼ばれる必要があるため、ビルトインロックを使用する方が比較的安全です。同時に、将来的には同期されているのではなく、同期化されたパフォーマンスを改善する可能性が高くなります。 SynchronizedはJVMの組み込みプロパティであるため、スレッド密閉ロックオブジェクトのロック除去最適化、ロックの粒度を高めることにより組み込みロックの同期を排除するなど、いくつかの最適化を実行できます。したがって、いくつかの高度な機能が必要な場合は、ReentrantLockを使用する必要があります。これには、Timable、Pollable、およびFutrudable Lock取得操作、公正キュー、および非ブロック構造ロックが含まれます。それ以外の場合、同期して最初に使用する必要があります。
3。ロックの取得とリリースの操作
まず、ReentrantLockを使用してロックを追加したサンプルコードを見てみましょう。
public void dosomething(){//デフォルトは、非フェアロックreentrantlock lock = new ReentrantLock()を取得することです。 {// rock.lock()を実行する前にtry {// lock.lock()を試してください。 //操作を実行します...}最後に{// lock.unlock()最終的にリリース。 }}以下は、ロックを取得およびリリースするためのAPIです。
// lock lock public void lock(){sync.lock();} // [sync.release(1);}の解放ロックpublic void lock(){sync.release()の操作の操作ロックを取得してロックを解放する操作が、それぞれ同期オブジェクトのロックメソッドとリリース方法に委任されていることがわかります。
Public Class ReentrantLockはLock、java.io.serializable {private final sync sync;要約静的クラス同期は、AbstractQueuedSynchronizer {Abstract void lock(); } //非フェアロック静的最終クラスNon-FairSync extends Syncを実装するSynchronizer {final void lock(){...}} //フェアロック静的最終クラスFairSync extends sync {...}}}}}}各reentrantLockオブジェクトには、タイプの同期の参照があります。この同期クラスは、抽象的な内部クラスです。 Abstractqueuedsynchronizerから継承します。内部のロック方法は抽象的な方法です。 ReentrantLockのメンバー変数同期には、建設中に値が割り当てられます。 ReentrantLockの2つのコンストラクター方法が何をするかを見てみましょう。
//デフォルトのparameterless constructor public reentrantlock(){sync = new non -fairsync();} //パラメーター化されたコンストラクターpublic reentrantlock(boolean fair){sync = fair? new fairsync():new nonfairsync();}デフォルトのパラメーターレスコンストラクターを呼び出すと、NonFairSyncインスタンスが同期するように割り当てられ、ロックは現時点では非フェアロックです。パラメーターコンストラクターを使用すると、パラメーターがFairSyncインスタンスを割り当てるか、同期して非フェアシンインスタンスを割り当てるかを指定できます。非フェアシンとフェアシンはどちらも同期クラスから継承され、lock()メソッドを書き直したため、ロックを取得する方法には、フェアロックと非フェアロックの間にいくつかの違いがあります。ロックのリリースの操作を見てみましょう。 Unlock()メソッドを呼び出すたびに、sync.release(1)操作を実行するだけです。この操作は、abstractqueuedsynchronizerクラスのリリース()メソッドを呼び出します。もう一度確認しましょう。
//ロック操作(排他的モード)のリリースパブリックファイナルブールリリース(int arg){//パスワードロックを回して、(tryrelease(arg)){//ヘッドノードノードh = headを取得します。 //ヘッドノードが空でなく、待機状態が0に等しくない場合、後継ノードを覚ます(h!= null && h.waitstatus!= 0){//後継ノードUnparksuccessess(h); } trueを返します。 } falseを返します;}このリリース方法は、AQSが提供するロック操作をリリースするためのAPIです。まず、TryReleaseメソッドを呼び出して、ロックを取得しようとします。 TryReleaseメソッドは抽象的なメソッドであり、その実装ロジックはサブクラス同期にあります。
//ロック保護された最終的なブールアレントリレアス(intリリース){int c = getState() - リリースをリリースしてみてください。 //ロックを保持しているスレッドが現在のスレッドではない場合、例外がスローされます(thread.currentthread()!= getExclusiveOntherthRead()){Throw New IllegalMonitorStateException(); } boolean free = false; //同期ステータスが0の場合、(c == 0){//ロックのフラグをtrue free = trueとして設定する場合、ロックが解放されることを意味します。 //占領されたスレッドを空のsetexclusiveownthread(null)に設定します。 } setState(c);無料で返品;}このTRYRELEASEメソッドは、最初に現在の同期状態を取得し、通過したパラメーターから新しい同期状態まで現在の同期状態を減算し、新しい同期状態が0に等しいかどうかを判断します。次に、ロックのリリース状態をtrueに設定し、現在ロックを占有しているスレッドをクリアし、最後にsetStateメソッドを呼び出して新しい同期状態を設定し、ロックのリリース状態を返します。
4.フェアロックと不公平なロック
同期に基づいて指し示すReentrantLockがどの特定のインスタンスであるかを知っています。建設中、メンバー変数同期が割り当てられます。値が非fairsyncインスタンスに割り当てられている場合、それはそれがフェア以外のロックであることを意味し、値がFairSyncインスタンスに割り当てられている場合、それは公正なロックであることを意味します。公正なロックである場合、スレッドはリクエストを行う順序でロックを取得しますが、不公平なロックでは、カットインの動作が許可されます。スレッドが不公平なロックを要求すると、ロックの状態がリクエストが発行されると同時に利用可能になった場合、スレッドはロックのすべての待機スレッドをスキップして直接獲得します。不公平なロックを取得する方法を見てみましょう。
//不公平なシンクロナイザー静的最終クラスNon -FairSync extends Sync {//親クラスの要約メソッドを実装してロック最終void lock()を取得する{// CASメソッドを使用して同期状態を設定します(比較(0、1)){//設定が成功した場合、ロックは整理されていないことを意味します。 } else {//それ以外の場合は、ロックが占有されていることを意味し、コールして獲得して、キューをキューに同期させて獲得(1)を取得するためにキューを同期させます。 }} //ロック保護された最終ブールトリックアを取得しようとする方法(int quickires){return non -fairtryacquire(取得); }} //非インタルストモードでロックを取得します(排他モード)パブリックファイナルvoid acchire(int arg){if(!tryacquire(arg)&& quickirequeued(addwaiter(node.exclusive)、arg)){self interrupt(); }}不公平なロックのロック方法では、スレッドがCASの最初のステップで同期状態の値を0から1に変更することがわかります。実際、この操作はロックを取得しようとするのと同等です。変更が成功した場合、スレッドが現在ロックを取得したことを意味し、同期キューにキューする必要はなくなりました。変更が失敗した場合、スレッドが最初に登場したときにロックがリリースされていないことを意味するため、取得方法が次に呼び出されます。この獲得方法は、抽象的な陰謀法から継承されていることがわかっています。この方法を確認しましょう。スレッドが取得方法に入ると、最初のコールはトリックアメソッドを呼び出してロックを取得しようとします。 Non -FairSyncはTryAcquireメソッドを上書きし、メソッドの親クラス同期の非フェアトリックワイアメソッドを呼び出すため、ロックを取得しようとするために、ここでは非授乳メソッドがここで呼び出されます。この方法が具体的に何をするか見てみましょう。
//ロックファイナルブール以外のNon -FairtryAcquireの不当な取得(int quickires){//現在のスレッド最終スレッドcurrent = thread.currentthread()を取得します。 //現在の同期状態を取得しますint c = getState(); //同期状態が0の場合、(c == 0){// casを使用して同期状態を更新する場合、ロックが占有されないことを意味します(比較(0、取得)){//現在ロックSetexclusiveOwnThread(現在)を占有しているスレッドを設定します。 trueを返します。 } //それ以外の場合、ロックが現在のスレッドであるかどうかが決定されますif(nextc <0){throw new error( "最大ロックカウントを超えた"); } setState(nextc); trueを返します。 } //ロックが現在のスレッドではない場合、故障フラグを返しますfalse;}Non -FairtryAcquireメソッドは同期メソッドです。スレッドがこの方法に入った後、最初に同期状態を取得することがわかります。同期状態が0の場合、CAS操作を使用して同期状態を変更します。実際、これは再びロックを取得することです。同期状態が0でない場合、ロックが占有されていることを意味します。この時点で、まずロックを保持しているスレッドが現在のスレッドであるかどうかを判断します。その場合、同期状態は1増加します。そうしないと、ロックを取得しようとする操作が失敗します。そのため、AddWaiterメソッドが呼び出され、同期キューにスレッドを追加します。要約すると、不公平なロックモードでは、スレッドが同期キューに入る前に2つのロックを取得しようとします。買収が成功した場合、同期キューキューキューキューを入力しません。そうしないと、同期キューキューキューキューが入力されます。次に、公正なロックを取得する方法を見てみましょう。
//フェアロック静的最終クラスFairSync extends sync {//親クラスの抽象メソッドを実装してロック最終void lock()を取得する{// call call quicireを獲得し、queueeをqueueして獲得する(1); } //ロック保護された最終的なブールアッキーを取得してみてください(int quickires){//現在のスレッド最終スレッドcurrent = thread.currentthread()を取得します。 //現在の同期状態を取得しますint c = getState(); //同期状態0は、(c == 0){//同期キューにフォワードノードがあるかどうかを防御する場合、ロックが占有されていないことを意味します(!hasqueuedpredsetState(0、取得)) setExclusiveOwnerThread(current); trueを返します。 } //それ以外の場合、現在のスレッドがロックを保持するかどうかを判断しますif(nextc <0){throw new error( "最大ロックカウントを超えた"); } setState(nextc); trueを返します。 } //現在のスレッドがロックを保持しない場合、取得はfalseを返します。 }}フェアロックのロック方法を呼び出すと、獲得方法は直接呼び出されます。同様に、取得方法は、最初にFairSyncを呼び出して、TryAcquireメソッドを書き直してロックを取得しようとします。この方法では、同期状態の値が最初に取得されます。同期状態が0の場合、この時点でロックが解放されることを意味します。不公平なロックとの違いは、最初にHasqueuedPrededersorsメソッドを呼び出して、誰かが同期キューでキューインしているかどうかを確認することです。誰もキューイングしていない場合、同期状態の値が変更されます。フェアロックは、すぐにロックを取得する代わりに、ここで礼儀正しい方法を採用していることがわかります。不公平なロックとは異なるこのステップを除いて、他の操作は同じです。要約すると、フェアロックは、同期キューに入る前にロックのステータスを1回だけチェックすることがわかります。ロックが開いていることがわかったとしても、すぐにそれを取得することはありません。代わりに、同期キュー内のスレッドに最初に取得します。したがって、すべてのスレッドがフェアロックの下でロックを取得する順序が最初に到着してから到着することを保証できます。これにより、ロックを取得する公平性も保証されます。
それで、なぜ私たちはすべてのロックを公平にしたいのですか?結局のところ、公平性は良い行動であり、不公平は悪い行動です。スレッドのサスペンドおよびウェイクアップ操作には大きなオーバーヘッドがあるため、特に激しい競争の場合にはシステムのパフォーマンスに影響を与えます。公正なロックは、頻繁に停止して糸の操作を行いますが、フェア以外のロックはそのような操作を減らすことができるため、パフォーマンスの公正なロックよりも優れています。さらに、ほとんどのスレッドはロックを非常に短時間使用し、スレッドのウェイクアップ操作に遅延が発生するため、スレッドBがすぐにロックを取得し、使用後にロックを解放する可能性があります。これは、双方にとって有利な状況につながります。スレッドAがロックを取得する瞬間は遅延しませんが、スレッドBはロックを事前に使用し、そのスループットも改善されました。
5。条件付きキューの実装メカニズム
組み込み条件キューにはいくつかの欠陥があります。各組み込みロックには、関連する条件キューが1つしかないため、複数のスレッドが同じ条件キューにある異なる条件の述語を待機させます。その後、NotifyAllが呼び出されるたびに、すべての待機スレッドが目覚めます。スレッドが目覚めると、それが待っている状態の述語ではなく、吊り下げられることがわかります。これにより、多くの役に立たないスレッドウェイクアップおよび停止操作が発生し、多くのシステムリソースを無駄にし、システムのパフォーマンスを低下させます。複数の条件付き述語で同時オブジェクトを書きたい場合、または条件付きキューの可視性よりも多くの制御を獲得したい場合は、組み込みのロックと条件付きキューの代わりに明示的なロックと状態を使用する必要があります。条件キューや内蔵ロックのように、条件とロックが一緒に関連付けられています。条件を作成するには、関連するロックのlock.newconditionメソッドを呼び出すことができます。まず、条件を使用した例を見てみましょう。
public class boundedbuffer {final lock = new ReentrantLock();最終条件notfull = lock.newcondition(); //条件述語:notfull最終条件Notempty = lock.newcondition(); //条件述語:notempty final object [] items = new Object [100]; int putptr、takptr、count; // production method public void put(object x)throws arturtedexception {lock.lock(); try {while(count == items.length)notfull.await(); //キューはいっぱいで、スレッドは著名なキューのアイテム[putptr]を待っています。アイテム[putptr] = x; if(++ putptr == items.length)putptr = 0; ++カウント; notempty.signal(); //生産が成功し、Notempty Queueのノードを覚ます}最後に{lock.unlock(); }} //消費方法public object take()throws arturnedexception {lock.lock(); try {while(count == 0)notempty.await(); //キューは空です。スレッドは、Notemptyキューのオブジェクトx =アイテム[dakptr]を待ちます。 if(++ takeptr == items.length)takptr = 0; - カウント; notfull.signal(); //消費が成功し、著名なキューリターンxのノードを覚ます。 }最後に{lock.unlock(); }}}ロックオブジェクトは複数の条件のキューを生成でき、ここでは2つの条件キューが生成されます。コンテナがいっぱいの場合、PUTメソッドを呼び出すスレッドをブロックする必要があります。状態の述語が真であるまで待ってください(コンテナが満たされていません)目覚めて実行を続けます。コンテナが空の場合、テイクメソッドを呼び出すスレッドをブロックする必要があります。状態の述語が真であるまで待ってください(コンテナが空ではありません)目覚めて実行を続けます。これらの2種類のスレッドは、異なる条件の述語に応じて待機するため、2つの異なる条件キューを入力してブロックし、適切なタイミングまで待機してから、条件オブジェクトのAPIを呼び出して目を覚まします。以下は、NewConditionメソッドの実装コードです。
//条件の作成public public条件Newcondition(){return sync.newcondition();} abstract static class sync extends abstractqueeuedsynchronizer {//新しい条件オブジェクト最終条件newcondition(){return new condysObject(); }}ReentrantLockでの条件キューの実装は、AbstractqueuedSynchronizerに基づいています。 NewConditionメソッドを呼び出すときに取得する条件オブジェクトは、AQSの内部クラスの状態オブジェクトのインスタンスです。条件のキューに関するすべての操作は、ConditionObjectによって提供されるAPIを呼び出すことによって行われます。 ConditionObjectの特定の実装については、私の記事「Java Concurrency Series [4] ----- AbstractQueuedSynchronizerコード分析条件付きキュー」を確認できます。ここでは繰り返しません。この時点で、ReentrantLockのソースコードの分析が終了しました。この記事を読むことで、読者がReentrantLockを理解し、マスターするのに役立つことを願っています。
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。