生産者の消費者パターンは、マルチスレッドの中で最も一般的なパターンです。プロデューサーのスレッド(1つ以上)はパンを生成し、バスケット(セットまたはアレイ)に入れます。それらには異なるタスクがありますが、それらが処理するリソースは同じであり、これはスレッド間通信の方法を反映しています。
この記事では、最初に単一の生産者と単一の消費者の状況を説明し、次にマルチプロデューサーとマルチ消費者モデルの状況を説明します。これらの2つのモードは、それぞれwait()/nofity()/nofityall()メカニズムとlock()/lock()メカニズムを使用して実装されます。
パターンの導入を開始する前に、wait()、notify()、およびnotifyall()メソッドの使用状況の詳細と、lock()/lock()、await()/signal()/signalall()の使用の改善を説明します。
1。待機と目覚めのメカニズムの原則
wait()、notify()、およびnotifyall()は、それぞれ睡眠に入り、睡眠スレッドを起動し、すべての睡眠スレッドを目覚めさせるスレッドを表します。しかし、オブジェクトはどのスレッドですか?さらに、APIドキュメントで説明されている3つのメソッドはすべて、有効なモニターの前提(ロックを保持すると理解できます)の下で使用する必要があります。これらの3つの方法はロックと何の関係がありますか?
同期コードブロックの同期(OBJ){}または同期関数は、すべてロックを保持しているため、コード構造で使用できます。
次の2つの同期コードブロックでは、それぞれロックOBJ1とロックOBJ2が使用されます。スレッド1とスレッド2は、OBJ1に対応する同期コードを実行し、スレッド3とスレッド4はOBJ2に対応する同期コードを実行します。
クラスMyLockを実装してRunnable {public int flag = 0;オブジェクトobj1 = new object();オブジェクトobj2 = new object(); public void run(){while(true){if(flag%2 = 0){synchronized(obj1){// threads t1とt2はこの同期タスクを実行します// try {obj1.wait();} catch(arturtedexception i){}} //obj1.notify()//obj1.notifyall()}同期(obj2){//スレッドT3とT4この同期タスクを実行する// try {obj2.wait();} catch(arternedexception i){} //obj2.notify()//obj2.notifyall()}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} ml = new mylock();スレッドT1 =新しいスレッド(ml);スレッドT2 =新しいスレッド(ml);スレッドT3 =新しいスレッド(ml);スレッドT4 =新しいスレッド(ml); t1.start(); t2.start(); try {thread.sleep(1)} catch(arternedexception i){}; ml.flag ++; t3.Start(); t4.start(); }}T1が待機()に実行を開始すると、睡眠状態に入りますが、通常の睡眠ではありませんが、OBJ1によって識別されたスレッドプールで睡眠をとることができます(実際、モニターはスレッドプールに対応しますが、モニターとロックはこの時点で結合されます)。 T2が実行を開始すると、ロックOBJ1は他のスレッドに保持され、睡眠状態に入ることがわかります。今回は、ロックリソースがwait()によって入力された睡眠ではなく、待機しているためです。 T2はすでにOBJ1ロックを適用していると判断しているため、通常の睡眠ではなく、OBJ1スレッドプールの睡眠にも入ります。同様に、T3とT4では、これら2つのスレッドがOBJ2スレッドプールに入ってスリープします。
スレッドがnotify()に実行されると、このnotify()は、ロックに対応するスレッドプールのスレッドをランダムに覚醒させます。たとえば、obj1.notify()は、obj1スレッドプールの睡眠スレッドを目覚めさせます(もちろん、睡眠スレッドがない場合は何もしません)。同様に、NotifyAll()は、ロックの対応するスレッドプールのすべての睡眠スレッドを目覚めさせます。
wait()、notify()、およびnotifyall()を呼び出すときにロックを明示的に指定する必要があるため、把握する必要があるのは「対応するロック」です。たとえば、obj1.wait()。ロックが省略されている場合、このオブジェクト、つまり、これらの3つのメソッドの接頭辞は、非静的同期関数でのみ省略できます。
要するに、同期を使用すると、ロックが使用され、スレッドには家があり、そのすべての根拠は帰属ロックによって決定されます。たとえば、スレッドの同期の場合、ロックがその後のコードを実行するかどうかを決定するためにロックがアイドル状態であるかどうかを決定し、特定のスレッドプールに移動して睡眠をとるかどうかを決定します。目覚めたとき、それはロックに対応するスレッドプールのスレッドのみを目覚めさせます。
これらのメソッドの適用では、通常はタスクで、wait()and notify()/notifyall()がペアで表示され、1つずつ実行されます。言い換えれば、アトミック同期実行のこのラウンド中に、wait()がスリープ状態に実行されるか、notify()が実行されてスレッドプールの睡眠スレッドを目覚めさせます。選択的な実行を達成するために、判断の基礎としてマーキングを使用することを検討できます。次の例を参照してください。
2.ロックと状態
Wait()シリーズの3つの方法は、睡眠と目覚めの両方のアクションがロックと完全に結合されているため、非常に限られています。たとえば、ロックOBJ1に関連付けられたスレッドは、OBJ1スレッドプールのスレッドのみを覚ますことができますが、ロックOBJ2に関連付けられたスレッドを起動することはできません。たとえば、同期化された同期が元々同期された場合、同期が開始されたときにロックが暗黙的に自動的に取得され、タスク全体が実行された後、ロックを暗黙的に自動的にリリースしました。
JDK 1.5から始めて、Javaはjava.util.concurrent.locksパッケージを提供します。最初の2つのインターフェイスは、ロックとモニターの方法(睡眠、ウェイクアップ操作)を分離します。ロックインターフェイスはロックのみを提供します。ロックメソッドNewConditon()を介して、ロックに関連付けられた1つ以上のモニターを生成できます。各モニターには、独自の睡眠と目覚めの方法があります。言い換えれば、ロックは同期されたメソッドと同期コードブロックの使用を置き換え、条件はオブジェクトモニターメソッドの使用を置き換えます。
下の図に示すように:
スレッドがcondition1.await()を実行すると、スレッドは条件1モニターに対応するスレッドプールに入り、スリープします。条件1.signal()が実行されると、条件1スレッドプールのスレッドはランダムに目覚めます。条件1.signalall()が実行されると、条件1スレッドプールのすべてのスレッドが目覚めます。同様に、条件2モニターにも同じことが言えます。
同じロックオブジェクトに関連付けられている限り、複数のモニターがある場合でも、他のスレッドをモニター全体で動作させることができます。たとえば、条件1のスレッドは条件2.signal()を実行して、条件2スレッドプールのスレッドを覚ますことができます。
このロックとモニターの関連性の方法を使用するには、次の手順を参照してください。
java.util.concurrent.locks。*; lock l = new Reentrantlock(); cons1 = l.newcondition();条件con2 = l.newcondition(); l.lock(); try {//コードセグメント( //コードセグメントは異常である可能性があるため、Unlock()を実行する必要があります。特定の使用法については、後でロックと状態のサンプルコードをご覧ください。
3.単一の生産者単一消費者モデル
プロデューサースレッド、消費者スレッド。生産者が生産する各パンについて、それをプレートに置いて、消費者は消費のためにプレートからパンを取り出します。生産者が生産を継続するかどうかを判断する根拠は、プレートにパンがないということですが、消費者が消費するかどうかを判断する根拠は、プレートにパンがあるということです。このモードでは、1つのパンのみが常にプレートに置かれているため、プレートを省略でき、生産者と消費者は段階的にパンを引き渡すことができます。
まず、これらの3つのカテゴリを説明する必要があります。1つは複数のスレッド(パンです)で動作するリソース、2つ目はプロデューサー、3つ目は消費者です。次の例では、それぞれプロデューサーと消費者のクラスにパンを生産し、パンを消費する方法をカプセル化します。これは、パンクラスにカプセル化されている場合に理解しやすいです。
//説明リソース:パンクラスのパンの数で決定されるパンの名前と数{public string name; public int count = 1;パブリックブールフラグ= false; //このマークは、wait()and notify()}の判断マークを提供します。これを確実にするために、//パンクラスはシングルトンパターンに従って設計できます。または、建設方法を通じて同じパンオブジェクトを生産者と消費者に渡すことができます。後者の方法はここで使用されます。 //プロデューサークラスのプロデューサーを説明してくださいRunnable {Private Bread B; //プロデューサーのメンバー:プロデューサーを処理したいリソース(パンB){this.b = b; } //パンを生産する方法を提供する公開voidプロデュース(string name){b.name = name + b.count; b.count ++; } public void run(){while(true){synchronized(bread.class){// bread.classをロック識別子として使用して、生産者と消費者の同期コードブロックが同じロックを使用できるif(b.flag)を使用できる{// wait()は同期コードブロックの内側にある必要があります。 try {bread.class.wait();} catch(arturnedexception i){}} produce( "Bread"); System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // notify()も同期する必要があります。そうしないと、ロックがリリースされ、ウェイクアップアクションは実行できません// ps:wait()and notify()は実行する必要があります。 this.b = b; System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ //プロデューサーとコンシューマープロデューサーに合わせます(b)。最終的な実行結果を作成して消費する必要があり、これは連続サイクルです。次のように:
Thread-0---Producer----Bread1Thread-1---Consumer-------Bread1Thread-0---Producer----Bread2Thread-1--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4.ロックと状態を使用して、単一の生産モデルと消費モデルを実現する
コードは次のとおりです。
java.util.concurrent.locks。*; class Bread {public string name; public int count = 1;パブリックブールフラグ= false; //プロデューサーと消費者に同じロックオブジェクトと同じ条件オブジェクトを提供しますpublic static lock = new ReentrantLock(); public static条件条件= lock.newcondition();}クラスプロデューサーは実行可能{プライベートパンB;プロデューサー(パンB){this.b = b; } public void produce(string name){b.name = name + b.count; b.count ++; } public void run(){while(true){// bread.lockを使用してリソースパンをロックします。lock.lock(); try {if(b.flag){try {bread.condition.await();} catch(arturtedexception i){}}生成( "パン"); System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- bread.signal(); try {if(!b.flag){try {bread.condition.await();} catch(arturnedexception i){}} System.out.println(Thread.currentThread().getName()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- //プロデューサーとコンシューマープロデューサーに合わせます(b)。5。マルチプロダクションおよび消費モデル(単一パン)
ここでは、最初に複数の生産者と複数の消費者のモデルを説明しますが、最大で1つのパンを同時に説明します。このモデルは現実には理想的ではないかもしれませんが、実際のマルチプロダクションと複数の消費モデルにつながるためには、このモデルをここで説明し、このモデルを分析し、単一生産コードと単一消費コードからどのように進化したかを分析する必要があると思います。
下の図に示すように:
単一の生産と単一の消費から複数の生産、複数の消費まで、マルチスレッドの安全性の問題とデッドロックの問題により、考慮する必要がある2つの問題があります。
一方の当事者にとって、マルチスレッドはシングルスレッドと同じ生産または消費能力をどのように達成できますか?言い換えれば、マルチスレッドの外観をシングルスレッドにする方法。マルチスレッドとシングルスレッドの最大の違いは、マルチスレッドの安全性の問題です。したがって、マルチスレッドによって実行されるタスクを確実に同期させることができる限り。
最初の質問は、一方の当事者のマルチスレッドの問題を考慮し、2番目の質問では、2つの当事者がどのように調和して生産と消費を完了することができるかを考慮します。つまり、反対側がアクティブである間に、生産者と消費者の片側が眠っていることを保証する方法です。一方の当事者が同期タスクの実行を終了したときに、他方の当事者を目覚めさせるだけです。
実際、単一のスレッドからマルチスレッドまで、検討する必要がある2つの問題があります:同期外とデッドロック。 (1)プロデューサーと消費者側の両方がマルチスレッドを持っている場合、プロデューサーのマルチスレッドは全体としてスレッドと見なすことができ、消費者側のマルチスレッドも全体として、スレッドの安全性の問題を解決します。 (2)生産全体と消費者全体を組み合わせることで、デッドロックの問題を解決するためのマルチスレッドと見なされます。 Javaのデッドロックを解決する方法は、相手を目覚めさせるか、すべてを目覚めさせることです。
問題は、特定の当事者の複数のスレッド間の同期を確実にする方法です。単一の消費者のコードは、例としてマルチスレッド実行によって分析されます。
while(true){synchronized(bread.class){if(!b.flag){try {bread.class.wait();} catch(arturnedexception i){}} System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------消費スレッド1がパンを消費した後、消費スレッド2を起こし、ループを続け、(!フラグ)が待機し、ロックが解放される場合に、判断し続けているとします。 CPUが消費者スレッド2を選択するだけであると仮定すると、消費者スレッド2も待機します。プロデューサーがパンを1杯生産すると、消費スレッド1が目覚めていると仮定して、待機ステートメントから新しく生産されたパンを消費し続け、消費スレッド2が再び目覚めていると仮定します。 CPUによって消費スレッド2が選択されると、消費スレッド2も待機ステートメントから下向きに消費し、生産されたパンは消費されます。問題は再び発生します。継続的に目覚めた消費スレッド1と2は同じパンを消費します。つまり、パンが繰り返し消費されます。これは、別のマルチスレッドアウトシンクの問題です。
長い間それについて話した後、視線を拡大した後に分析するのは実際には非常に簡単です。一方の当事者の2つ以上のスレッドが判断B.FLAGを待つ限り、2つ以上のスレッドが継続的に目覚め、生産または下向きに生成または消費される可能性があります。これにより、マルチスレッドアウト同期の問題が生じます。
不安の問題は、同じ当事者の複数のスレッドが継続的な目覚めの後も下向きに生成または消費し続けているという事実にあります。これは、IFステートメントによって引き起こされます。待機スレッドが戻って、b.flagが目覚め後に真であるかどうかを判断できる場合、待機するか、生産を続けるか、下向きの消費を決定するかを決定できます。
IFステートメントを、要件を満たすためにHOWステートメントに置き換えることができます。このようにして、特定の当事者の複数のスレッドが継続的に目覚めているかどうかに関係なく、B.Flag判事に戻ります。
while(true){synchronized(bread.class){while(!b.flag){try {bread.class.wait();} catch(interruptedexception i){}} System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------最初のマルチスレッド安全性の問題は解決されましたが、デッドロックの問題が発生しました。これは簡単に分析できます。プロデューサーは全体と見なされており、消費者も全体です。プロデューサーのスレッドがすべて待っているとき(プロダクションパーティーのスレッドは継続的に目覚め、パーティーのすべてのスレッドが待機します)、消費者も待っており、デッドロックが表示されます。実際、増幅された方法で見ると、生産者と消費者はそれぞれ1つのスレッドと見なされます。これらの2つのスレッドは複数のスレッドを形成します。片側が待って反対側を目覚めることができないとき、もう一方の側は間違いなく待つので、それはデッドロックされます。
一方の当事者の継続的な目覚めではなく、他の当事者を目覚めさせることができる限り、両当事者間のデッドロックの問題のために、それは解決できます。 notifyall()またはsignalall()を使用するか、signal()を介して他のスレッドを起動して問題を解決できます。以下の2番目のコードを参照してください。
上記の分析によると、単一の生産コードと単一の消費モデルのコードが改善された場合、マルチプロダクションおよびマルチ消費シングルパンモデルに変更できます。
//コードセグメント1Class Bread {public String name; public int count = 1;パブリックブールフラグ= false; } //プロデューサークラスのプロデューサーを説明してくださいRunnable {Private Bread B;プロデューサー(パンB){this.b = b; } public void produce(string name){b.name = name + b.count; b.count ++; } public void run(){while(true){synchronized(bread.class){while(b.flag){try {bread.class.wait();} catch(interruptedexception i){}}生成( "Bread"); System.out.println(Thread.currentThread().getName()+"--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- string consumption(){return b.name; public void run(){synchronized(bread.class){while(!b.flag){try {bread.class.wait();} catch(interruptedexception i){}}} System.out.println(Thread.currentThread().getName()+"--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- } public cossume_5 {String [] arging {//スレッドCon_t1./ Thread con_t2()以下は、lockとconditonを使用してリファクタリングされたコードを、信号()を使用して他のスレッドを目覚めさせます。
//コードセグメント2Import Java.util.concurrent.Locks。*; class Bread {public String name; public int count = 1;パブリックブールフラグ= false; public static lock = new ReentrantLock(); public static条件pro_con = lock.newcondition(); public static条件con_con = lock.newcondition();} //プロデューサークラスのプロデューサーを説明するRunnable {private Bread b;プロデューサー(パンB){this.b = b; } public void produce(string name){b.name = name + b.count; b.count ++; } public void run(){while(true){bread.lock.lock(); try {while(b.flag){try {bread.pro_con.await();} catch(arturtedexception i){}}農産物( "パン"); System.out.println(Thread.currentThread().getName()+"----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- bread.con.signal(); bread.lock.lock(); try {!b.flag){try {bread.con_con.await();} System.out.println(Thread.currentThread().getName()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- //プロデューサープロデューサー(b) con_t1.start();より多くの生産とより多くの消費の問題を要約しましょう:
(1)。特定の当事者のマルチスレッドアウト同期の解決策は、while(flag)を使用して待機するかどうかを判断することです。
(2)。両当事者のデッドロック問題の解決策は、相手を目覚めることです。 notifyall()、signalall()、または相手のモニターの信号()メソッドを使用できます。
6.より多くの生産モデルと消費モデル
複数のプロデューサースレッドと複数の消費者スレッドがあります。生産者は生産されたパンをバスケット(セットまたはアレイ)に入れ、消費者はバスケットからパンを取り出します。生産者が継続的な生産を判断する根拠は、バスケットがいっぱいであり、消費者が継続的な消費を判断する根拠は、バスケットが空であるかどうかです。さらに、消費者がパンを取り出すと、対応する位置が再び空になり、生産者はバスケットのポインターをリセットすることで達成できるバスケットの開始位置から生産を引き返して継続できます。
このモデルでは、生産者、消費者、パンの説明に加えて、バスケット容器を説明することも必要です。アレイがコンテナとして使用され、生産者が生産するたびに、生産ポインターが後方にシフトし、消費者が消費するたびに消費ポインターが後方にシフトするとします。
コードは次のとおりです。API->条件クラスで指定されたサンプルコードを参照できます
java.util.concurrent.locks。*; class Basket {private Bread [] arr; //バスケットバスケットのサイズ(intサイズ){arr = new Bread [size]; } //プライベートint in_ptr、out_ptrのポインター。 //バスケットのプライベートINTに残ったパンの残り。プライベートロックlock = new ReentrantLock();プライベート条件full = lock.newcondition();プライベート条件EMPTY = LOCK.NEWCONDITION(); //バスケットへのパンパブリックvoid in(){lock.lock(); try {while(left == arr.length){try {full.await();} catch(arturnedexception i){i.printstacktrace();}} arr [in_ptr] = new Bread( "mianbao"、producer.num ++); system.out.println( "パンをput:"+arr [in_ptr] .getname()+"-------バスケット["+in_ptr+"]");左++; if(++ in_ptr == arr.length){in_ptr = 0;} empty.signal(); }最後に{lock.unlock(); }} //バスケットからのパンパブリックパン(){lock.lock(); try {while(left == 0){try {empty.await();} catch(arturnedexception i){i.printstacktrace();}} bread out_bread = arr [out_ptr]; System.out.println( "パンを取得:"+out_bread.getName()+"---------- from basket ["+out_ptr+"]");左 - ; if(++ out_ptr == arr.length){out_ptr = 0;} full.signal(); Return Out_Bread; }最後に{lock.unlock(); }}} class Bread {private string name;パン(文字列名、int num){this.name = name + num; } public string getname(){return this.name; }}クラスプロデューサーはrunnable {プライベートバスケットバスケット; public static int num = 1; //パンの名前プロデューサーの最初の番号(バスケットB){this.basket = b; } public void run(){while(true){basket.in(); try {thread.sleep(10);} catch(arternedexception i){}}}} class Consumer Implesions runnable {private basket Basket;プライベートパンi_get;消費者(バスケットB){this.basket = b; } public void run(){while(true){i_get = basket.out(); try {thread.sleep(10);} catch(arturnedexception i){}}}} public class produceconsume_7 {public static void main(string [] args){basket b = new Basket(20); //バスケットサイズ= 20プロデューサーpro =新しいプロデューサー(b);消費者con = new Consumer(b);スレッドpro_t1 = newスレッド(pro);スレッドpro_t2 = newスレッド(pro);スレッドcon_t1 = newスレッド(con);スレッドcon_t2 = newスレッド(con);スレッドcon_t3 = newスレッド(con); pro_t1.start(); pro_t2.start(); con_t1.start(); con_t2.start(); con_t3.start(); }}これには、消費者、生産者、パン、バスケットが含まれ、パンとバスケットは複数のスレッドで運用されるリソースです。プロデューサーの糸はパンを生産し、それをバスケットに入れ、消費者の糸がバスケットからパンを取り出します。理想的なコードは、リソースクラスの生産タスクと消費タスクの両方をカプセル化することです。パンはバスケット容器の要素であるため、パンのクラスへのパッケージ化には適しておらず、バスケットへのパッケージングにより容器の操作が簡単になります。
ロック内にリソース操作を含むすべてのコードを配置する必要があることに注意してください。そうしないと、マルチスレッドアウトスネクロネーションの問題が発生します。たとえば、パンの製造メソッドは生産者クラスで定義され、バスケット、つまりバスケット.in()に入れられたメソッドバスケットのパラメーターとして使用されます。
上記の記事は、Javaプロデューサーおよびコンシューマーモデル(詳細な分析)に基づいており、エディターが共有するコンテンツ全体です。私はそれがあなたに参照を与えることができることを願っています、そしてあなたがwulin.comをもっとサポートできることを願っています。