目次(?)[-]
JavalangThread Class 2を拡張する2つの実装javalangrunnableインターフェイススレッドと実行可能な4つのスレッド状態遷移5スレッドスケジューリング6つの共通関数結合メソッドが使用される理由7共通スレッド名詞説明8スレッド同期9スレッドデータ送信
この記事では、主に、Javaでのマルチスレッドの使用方法、スレッドの同期、スレッドデータ転送、スレッドステータス、および対応するスレッド関数の使用と概要について説明しています。
まず、プロセスとスレッドの違いについて話しましょう。
プロセス:各プロセスには独立したコードとデータ空間(プロセスコンテキスト)があり、プロセス間の切り替えには大きなオーバーヘッドがあります。プロセスには1-nスレッドが含まれます。
スレッド:同じタイプのスレッドがコードとデータスペースを共有します。各スレッドには、独立したランニングスタックとプログラムカウンター(PC)があり、スレッドの切り替えオーバーヘッドは小さいです。
プロセスと同様に、スレッドは、作成、レディ、ランニング、ブロッキング、終了の5つの段階に分けられます。
マルチプロセスとは、オペレーティングシステムが複数のタスク(プログラム)を同時に実行できることを意味します。
マルチスレッドとは、同じプログラムで実行されている複数のシーケンシャルストリームを指します。
Javaでは、マルチスレッドを実装する2つの方法があります。 1つはスレッドクラスを続行することであり、もう1つは実行可能なインターフェイスを実装することです。
1. java.lang.threadクラスを拡張します
パッケージcom.multithread.learning;/***@cunton multithreading Learning*@著者lin bingwen*@time 2015.3.9*/class thread1 extends thread {private string name; public thread1(string name){this.name = name; } public void run(){for(int i = 0; i <5; i ++){system.out.println(name + "run:" + i); try {sleep((int)math.random() * 10); } catch(arturnedexception e){e.printstacktrace(); }}}} public class main {public static void main(string [] args){thread1 mth1 = new shood1( "a"); thread1 mth2 = new thread1( "b"); mth1.start(); mth2.start(); }}出力:
実行:0
bラン:0
実行:1
ラン:2
ラン:3
ラン:4
bラン:1
Bラン:2
Bラン:3
Bラン:4
もう一度実行:
実行:0
bラン:0
bラン:1
Bラン:2
Bラン:3
Bラン:4
実行:1
ラン:2
ラン:3
ラン:4
説明:
プログラムがメインの開始と実行のとき、Java仮想マシンはプロセスを開始し、メインスレッドメインはMain()が呼び出されたときに作成されます。 MITISAYの2つのオブジェクトの開始方法により、他の2つのスレッドも開始されるため、アプリケーション全体が複数のスレッドで実行されます。
注:start()メソッドは、マルチスレッドコードをすぐに実行するのではなく、スレッドを実行可能な状態にするために呼び出されます。実行する場合は、オペレーティングシステムによって決定されます。
実行中のプログラムの結果から、マルチスレッドプログラムが順調に実行されていることがわかります。したがって、順序外で実行されたコードのみをマルチスレッドとして設計する必要があります。
Thread.Sleep()メソッドの呼び出しの目的は、現在のスレッドがプロセスだけで得られたCPUリソースを占めることを防ぐために、他のスレッドが実行するために一定の時間を残すことです。
実際、すべてのマルチスレッドコードの実行順序は不確かであり、各実行の結果はランダムです。
ただし、開始方法が繰り返し呼ばれる場合、java.lang.illegalthreadstateExceptionが発生します。
thread1 mth1 = new thread1( "a"); thread1 mth2 = mth1; mth1.start(); mth2.start();
出力:
スレッド「Main」Java.lang.IllegalThreadStateExceptionの例外
Java.lang.thread.start(不明なソース)で
com.multithread.learning.main.main(main.java:31)
実行:0
実行:1
ラン:2
ラン:3
ラン:4
2。java.lang.runnableインターフェイスを実装します
/***@functon multithreading Learning*@著者lin bingwen*@time 2015.3.9*/package com.multithread.runnable; class thread2実装{private string name; public thread2(string name){this.name = name; } @Override public void run(){for(int i = 0; i <5; i ++){system.out.println(name + "run:" + i); try {thread.sleep((int)math.random() * 10); } catch(arturnedexception e){e.printstacktrace(); }}}} public class main {public static void main(string [] args){new Thread(new Thread2( "c"))。start();新しいスレッド(new Thread2( "d"))。start(); }}出力:
c実行:0
D実行:0
d実行:1
c run:1
d実行:2
c run:2
d実行:3
c run:3
d run:4
c run:4
説明:
Thread2クラスは、実行可能なインターフェイスを実装します。これにより、クラスにマルチスレッドクラスの特性があります。 run()メソッドは、マルチスレッドプログラムのコンベンションです。すべてのマルチスレッドコードは実行方法です。スレッドクラスは、実際には実行可能なインターフェイスを実装するクラスです。
マルチスレッドを開始するときは、最初にスレッドクラスコンストラクタースレッド(実行可能ターゲット)を介してオブジェクトを構築し、次にスレッドオブジェクトのstart()メソッドを呼び出してマルチスレッドコードを実行する必要があります。
実際、すべてのマルチスレッドコードは、Running Threadのstart()メソッドによって実行されます。したがって、スレッドクラスを拡張するか、実行可能なインターフェイスを実装してマルチスレッドを実装するか、最終的にスレッドオブジェクトAPIを介してスレッドを制御するかどうかにかかわらず、スレッドクラスAPIに慣れることは、マルチスレッドプログラミングの基礎です。
3。スレッドと実行可能な違い
クラスがスレッドを継承する場合、リソース共有には適していません。ただし、実行可能なインターフェイスが実装されている場合、リソース共有を簡単に実装できます。
パッケージcom.multithread.learning;/***@cunton multi-thread Learning、継承スレッド、リソースは共有できません*@著者lin bingwen*@time 2015.3.9*/class thread1 extends thread {private int count = 5;プライベート文字列名; public thread1(string name){this.name = name; } public void run(){for(int i = 0; i <5; i ++){system.out.println(name + "run count =" + count-); try {sleep((int)math.random() * 10); } catch(arturnedexception e){e.printstacktrace(); }}}} public class main {public static void main(string [] args){thread1 mth1 = new shood1( "a"); thread1 mth2 = new thread1( "b"); mth1.start(); mth2.start(); }}出力:
b run count = 5
ランカウント= 5
b run count = 4
b run count = 3
b run count = 2
b run count = 1
ランカウント= 4
実行数= 3
実行数= 2
実行数= 1
上記から、カウントは異なるスレッド間で異なることがわかります。これは、チケット販売システムに大きな問題を抱えています。もちろん、同期はここで使用できます。 Runnableを使用してここで実行しましょう
/***@functonマルチスレッド学習継承ランナブル、リソースは共有できます*@著者lin bingwen*@time 2015.3.9*/package com.multithread.runnable; class thread2実装{private int count = 15; @Override public void run(){for(int i = 0; i <5; i ++){system.out.println(thread.currentthread()。getName() + "run count =" + count-); try {thread.sleep((int)math.random() * 10); } catch(arturnedexception e){e.printstacktrace(); }}}}} public class main {public static void main(string [] args){thread2 my = new shood2();新しいスレッド(my、 "c")。start(); //同じMtですが、スレッドでは不可能です。オブジェクトMTをインスタンス化すると、例外が新しいスレッド(my、 "d")が表示されます。start();新しいスレッド(my、 "e")。start(); }}出力:
c run count = 15
d run count = 14
e run count = 13
d run count = 12
d run count = 10
d run count = 9
d run count = 8
c run count = 11
e run count = 12
c run count = 7
e run count = 6
c run count = 5
e run count = 4
c run count = 3
e run count = 2
ここでは、各スレッドが同じインスタンス化オブジェクトを使用することに注意する必要があります。同じでない場合、効果は上記と同じになります!
要約:
スレッドクラスを継承する上で実行可能なインターフェイスを実装することの利点:
1):同じリソースを処理するために同じプログラムコードを持つ複数のスレッドに適しています
2):Javaの単一相続の制限を回避できます
3):プログラムの堅牢性を高め、コードは複数のスレッドで共有でき、コードとデータは独立しています
思い出させてください:主な方法は実際にはスレッドです。 Javaでは、スレッドが同時に開始されます。最初にいつ実行されるかについては、誰が最初にCPUリソースを取得するかに完全に依存します。
Javaでは、プログラムが実行されるたびに少なくとも2つのスレッドが開始されます。 1つはメインスレッド、もう1つはガベージコレクションスレッドです。 Javaコマンドを使用してクラスが実行されるたびに、JVMが実際に開始され、各JVMインターンシップがオペレーティングシステムでプロセスを開始するためです。
4。スレッド状態遷移
1。新しい状態(新しい):新しいスレッドオブジェクトが作成されます。
2。ReadyState(実行可能):スレッドオブジェクトが作成された後、他のスレッドはオブジェクトのstart()メソッドを呼び出します。この状態のスレッドは、実行可能なスレッドプールにあり、実行可能になり、CPUの使用権を取得するのを待っています。
3。ランニング状態:Ready StateのスレッドはCPUを取得し、プログラムコードを実行します。
4。ブロック状態:ブロック状態とは、スレッドが何らかの理由でCPUの使用権を放棄し、一時的に実行を停止することを意味します。スレッドが準備が整った状態に入るまで、実行中の状態に行く機会がありません。閉塞には3つのタイプがあります。
(1)ブロックを待つ:実行中のスレッドはwait()メソッドを実行し、JVMはスレッドを待機中のプールに入れます。
(2)同期ブロッキング:実行中のスレッドがオブジェクトの同期ロックを取得すると、同期ロックが他のスレッドで占有されている場合、JVMはスレッドをロックプールに入れます。
(iii)、その他のブロッキング:実行中のスレッドがSleep()またはJoin()メソッドを実行する場合、またはI/O要求を発行する場合、JVMはスレッドをブロッキング状態に設定します。 Sleep()状態がタイムアウトしたとき、Join()がスレッドが終了またはタイミングアウトするのを待っているか、I/O処理が完了した場合、スレッドは準備ができた状態に再入力されました。
5。DeadState:スレッドは、例外のために実行()メソッドの実行を終了または終了し、スレッドはライフサイクルを終了します。
5。スレッドスケジューリング
スレッドスケジューリング
1.スレッドの優先順位を調整します:Javaスレッドには優先度があり、優先度が高いスレッドは実行する機会が増えます。
Javaスレッドの優先度は、1〜10の値範囲の整数で表されます。スレッドクラスには、次の3つの静的定数があります。
static int max_priority
スレッドが持つことができる最優先事項は10です。
静的int min_priority
スレッドが持つことができる最低の優先度は1です。
静的int norm_priority
スレッドに割り当てられるデフォルトの優先度は5です。
スレッドクラスのsetpriority()およびgetPriority()メソッドは、それぞれスレッドの優先度を設定して取得するために使用されます。
各スレッドにはデフォルトの優先度があります。メインスレッドのデフォルトの優先度は、thread.norm_priorityです。
スレッドの優先順位は継承されます。たとえば、スレッドBがスレッドAで作成された場合、BはAと同じ優先度を持ちます。
JVMは10のスレッド優先順位を提供しますが、一般的なオペレーティングシステムにはうまくマッピングされません。プログラムを各オペレーティングシステムに移植する必要がある場合は、次の3つの静的定数を持つスレッドクラスのみを優先順位として使用する必要があります。これにより、同じ優先度が同じスケジューリング方法を採用することができます。
2。スレッドスリープ:スレッド(長いミリ)方法を操作して、スレッドをブロッキング状態にします。 Millisパラメーターは、ミリ秒単位で睡眠時間を設定します。睡眠が終わると、実行可能になります。 Sleep()プラットフォームには良い携帯性があります。
3。スレッド待機:オブジェクトクラスのwait()メソッドは、他のスレッドがオブジェクトのnotify()メソッドまたはnotifyall()ウェイクアップメソッドを呼び出すまで現在のスレッドを待機させます。これらの2つのウェイクアップ方法は、オブジェクトクラスのメソッドでもあり、その動作は待機(0)を呼び出すことと同等です。
4。スレッドコンセッション:thread.yield()メソッドは、現在実行中のスレッドオブジェクトを一時停止し、実行することで同じまたは高い優先度を持つスレッドに実行されます。
5。スレッドJoin:Join()メソッド、他のスレッドが終了するのを待ちます。現在のスレッド内の別のスレッドのjoin()メソッドを呼び出すと、現在のスレッドは他のプロセスが実行され、現在のスレッドがブロック状態から対応状態になります。
6。スレッドウェイクアップ:オブジェクトクラスのnotify()メソッドは、このオブジェクトモニターで待機している単一のスレッドを起動します。すべてのスレッドがこのオブジェクトを待っている場合、スレッドの1つが選択されます。選択はarbitrary意的であり、実装に関する決定を下すときに発生します。スレッドは、待機方法のいずれかを呼び出すことにより、オブジェクトのモニターで待機します。 Wakenスレッドは、現在のスレッドがこのオブジェクトのロックを放棄するまで実行できません。 Wakenスレッドは、従来の方法でオブジェクトに積極的に同期される他のすべてのスレッドと競合します。たとえば、Wakenスレッドには、このオブジェクトをロックする次のスレッドであるという信頼できる特権や欠点はありません。同様の方法には、このオブジェクトモニターで待機しているすべてのスレッドを目覚めるnotifyall()もあります。
注:2つの方法は、JDK1.5で廃止されている()suspend()とresume()がJDK1.5で廃止されており、再び導入されません。デッドロックする傾向があるからです。
6。一般的な関数の説明
sleep睡眠(長いミリ):指定されたミリ秒以内に現在実行中のスレッドスリープを使用してください(実行中断)
②join():tスレッドが終了するのを待つことを指します。
それを使用する方法。
結合は、スレッドクラスの方法です。スレッドを起動した後に直接呼ばれます。つまり、join()の関数は次のとおりです。「スレッドが終了するのを待ちます」。ここで理解する必要があるのは、スレッドが子スレッドが終了するのを待っているメインスレッドを指すことです。つまり、ChildスレッドがJoin()メソッドを呼び出した後のコードであり、子スレッドが終了するまで実行することができます。
スレッドT = new Athread(); t.start(); T.Join();
Join()メソッドを使用する理由
多くの場合、メインスレッドは子スレッドを生成して起動します。子スレッドで多数の時間のかかる操作が必要な場合、メインスレッドは子スレッドの前に終了することがよくあります。ただし、メインスレッドが他のトランザクションの処理後にチャイルドスレッドの処理結果を使用する必要がある場合、つまり、メインスレッドは、終了する前に子スレッドが実行を完了するのを待つ必要があります。現時点では、Join()メソッドを使用する必要があります。
参加しません。 /** *@functon multithreading Learning、join *@著者lin bingwen *@time 2015.3.9 */package com.multithread.join; class thread1 extends thread {private string name; public thread1(string name){super(name); this.name = name; } public void run(){system.out.println(thread.currentthread()。getname() + "thread starts!"); for(int i = 0; i <5; i ++){system.out.println( "subthread"+name+"run:"+i); try {sleep((int)math.random() * 10); } catch(arturnedexception e){e.printstacktrace(); }} system.out.println(thread.currentthread()。getname() + "thread run end!"); }} public class main {public static void main(string [] args){system.out.println(thread.currentthread()。getName()+"メインスレッドランスタート!"); thread1 mth1 = new thread1( "a"); thread1 mth2 = new thread1( "b"); mth1.start(); mth2.start(); system.out.println(thread.currentThread()。getName()+ "メインスレッド実行End!"); }}出力結果:
メインのメインスレッドが実行を開始します!
メインのメインスレッドは実行されます!
bスレッドの実行が始まります!
チャイルドスレッドBは実行されます:0
スレッドランが始まります!
チャイルドスレッドAが実行されます:0
チャイルドスレッドBは実行されます:1
チャイルドスレッドAが実行されます:1
チャイルドスレッドAが実行されます:2
チャイルドスレッドAが実行されます:3
チャイルドスレッドAが実行されます:4
スレッドランは終了します!
チャイルドスレッドBは実行されます:2
チャイルドスレッドBは実行されます:3
チャイルドスレッドBは実行されます:4
bスレッドが走ります!
メインスレッドは子供のスレッドよりも早く終了したことがわかりました
参加する
public class main {public static void main(string [] args){system.out.println(thread.currentthread()。 thread1 mth1 = new thread1( "a"); thread1 mth2 = new thread1( "b"); mth1.start(); mth2.start(); {mth1.join(); } catch(arturnedexception e){e.printstacktrace(); } try {mth2.join(); } catch(arturnedexception e){e.printstacktrace(); } system.out.println(thread.currentthread()。getName()+ "メインスレッドが実行されます!"); }}実行結果:
メインのメインスレッドが実行を開始します!
スレッドランが始まります!
チャイルドスレッドAが実行されます:0
bスレッドの実行が始まります!
チャイルドスレッドBは実行されます:0
チャイルドスレッドAが実行されます:1
チャイルドスレッドBは実行されます:1
チャイルドスレッドAが実行されます:2
チャイルドスレッドBは実行されます:2
チャイルドスレッドAが実行されます:3
チャイルドスレッドBは実行されます:3
チャイルドスレッドAが実行されます:4
チャイルドスレッドBは実行されます:4
スレッドランは終了します!
メインスレッドは、子供のスレッドが終了する前に完成するまで間違いなく待ちます。
③yield():現在実行中のスレッドオブジェクトを一時停止し、他のスレッドを実行します。
thread.yield()メソッドの関数は次のとおりです。現在実行中のスレッドオブジェクトを一時停止し、他のスレッドを実行します。
利回り()がすべきことは、現在の実行スレッドを実行可能な状態に戻して、同じ優先度のある他のスレッドが実行された機会を得ることを許可することです。したがって、evel()を使用する目的は、同じ優先度のスレッドが適切に実行できるようにすることです。ただし、実際には、譲歩スレッドがスレッドスケジューラによって再び選択される可能性があるため、譲歩の目的を達成するために利回り()を確実にすることはできません。
結論:evel()がスレッドを待機/睡眠/ブロッキング状態に移動することはありません。ほとんどの場合、利回り()はスレッドを実行状態から実行して実行しますが、動作しない場合があります。上の写真を見ることができます。
/** *@funton multithreading Learning light *@著者Lin bingwen *@time 2015.3.9 */package com.multithread.yield; class threadyield extends thread {public threadyield(string name){super(name); } @Override public void run(){for(int i = 1; i <= 50; i ++){system.out.println( "" + this.getName() + "-----" + i); // 30の場合、スレッドはCPU時間を放棄し、他のスレッドまたは独自のスレッドを実行します(つまり、最初に実行する人は誰でも)if(i == 30){this.yield(); }}}} public class main {public static void main(string [] args){threadyield yt1 = new threadyield( "Zhang San"); threadyield yt2 = new threadyield( "li si"); yt1.start(); yt2.start(); }}実行結果:
最初のケース:Li Si(スレッド)は、30に実行されるとCPUのタイムアウトを取得します。この時点で、Zhang San(Thread)はCPU時間をつかんで実行します。
2番目の状況:Li Si(スレッド)が30に実行されると、CPU時間は放棄されます。この時点で、Li Si(スレッド)はCPU時間をつかんで実行します。
Sleep()とhieds()の違い
Sleep()とhieds())の違いにより、Sleep()により現在のスレッドが停滞状態に入ります。そのため、スリープを実行するスレッドは、指定された時間内に間違いなく実行されません。 evel()により、現在のスレッドが実行可能な状態に戻るだけで、実行可能状態に入った直後にexective execting hight()が実行される可能性があります。
睡眠方法により、現在実行されているスレッドが一定期間睡眠になり、払いのない状態になります。この期間の長さは、プログラムによって設定されます。利回り方式により、現在のスレッドはCPUの所有権を放棄できますが、転送の時間は確かではありません。実際、利回り()メソッドは次の操作に対応します。最初に、同じ実行可能状態にある同じ優先度を持つスレッドがあるかどうかを確認します。もしそうなら、このスレッドにCPUの所有権を引き渡します。そうしないと、元のスレッドの実行を続けます。したがって、利回り()メソッドは「コンセッション」と呼ばれ、同じ優先順位を持つ他のスレッドに実行の機会を与えます
さらに、睡眠方法により、優先度の低いスレッドが実行される可能性がありますが、evels()メソッドが実行されると、現在のスレッドはまだ実行可能な状態にあるため、後でCPUの所有権を取得するために低い優先度のスレッドを放棄することは不可能です。ランニングシステムでは、優先度の高いスレッドが睡眠方法を呼び出さず、I/Oによってブロックされていない場合、優先度の高いスレッドは、すべての優先度のスレッドが実行されるのを待って、実行するチャンスがあります。
setspriority():スレッドの優先度を変更します。
min_priority = 1
norm_priority = 5
max_priority = 10
使用法:
thread4 t1 = new thread4( "T1");
thread4 t2 = new thread4( "T2");
t1.setpriority(thread.max_priority);
t2.setpriority(thread.min_priority);
unterrupt():スレッドを中断します。この終了方法はかなり荒いです。 Tスレッドがリソースを開き、閉じる時間がなかった場合、つまり、実行方法が実行される前にスレッドを終了することを余儀なくされるため、リソースが閉じられなくなります。
プロセスを終了する最良の方法は、Sleep()関数のサンプルプログラムを使用することです。ブール変数は、run()メソッドが終了するときに制御するためにスレッドクラスで使用されます。 run()メソッドが終了すると、スレッドは終了します。
⑥wait()
obj.wait()およびobj.notify()は、同期(obj)で使用する必要があります。つまり、待機し、取得されたOBJロックで動作します。同期された観点から、それはobj.wait()であり、obj.notifyは同期(obj){...}ステートメントブロックにある必要があります。機能的な観点から、待機は、スレッドがオブジェクトロックを取得した後、オブジェクトロックを積極的に解放し、スレッドが眠ることを意味します。オブジェクトロックは取得できず、別のスレッドがオブジェクトNotify()を呼び出してスレッドを起動するまで実行されます。対応するnotify()は、オブジェクトロックのウェイクアップ操作です。しかし、注意すべきことの1つは、notify()コールの後、オブジェクトロックがすぐにリリースされないが、対応する同期(){}ステートメントブロックの実行が完了し、ロックが自動的にリリースされることです。JVMはWAIT()オブジェクトロックスレッドからスレッドをランダムに選択し、オブジェクトロックに割り当て、スレッドを起動し、実行を続行します。これにより、スレッド間の同期とウェイクアップ操作が提供されます。 shood.sleep()とobject.wait()の両方が、現在のスレッドを一時停止し、CPUコントロールを解放できます。主な違いは、object.wait()がCPUを解放する間、オブジェクトロックの制御を解放することです。
概念的に理解するだけでは十分ではなく、よりよく理解するために実用的な例でテストする必要があります。 object.wait()およびobject.notify()のアプリケーションの最も古典的な例は、3つのスレッドでABCを印刷する問題である必要があります。これは比較的古典的なインタビューの質問であり、質問は次のとおりです。
3つのスレッドを確立し、スレッドAを10回、スレッドBを10回印刷し、スレッドCを10回印刷し、スレッドCはスレッドが同時に実行される必要があり、ABCは10回交互に印刷されます。この問題は、ObjectのWait()とNotify()を使用して簡単に解決できます。コードは次のとおりです。
/** * wait usage * @author dreamsea * @time 2015.3.9 */package com.multithread.wait; public class mythreadprinter2を実装します{private string name;プライベートオブジェクト前;プライベートオブジェクトの自己; private mythreadprinter2(string name、object prev、object self){this.name = name; this.prev = prev; this.self = self; } @Override public void run(){int count = 10; while(count> 0){synchronized(prev){synchronized(self){system.out.print(name);カウント - ; self.notify(); } try {prev.wait(); } catch(arturnedexception e){e.printstacktrace(); }}}} public static void main(string [] args)throws exception {object a = new object();オブジェクトb = new object();オブジェクトc = new object(); mythreadprinter2 pa = new mythreadprinter2( "a"、c、a); mythreadprinter2 pb = new mythreadprinter2( "b"、a、b); mythreadprinter2 pc = new mythreadprinter2( "c"、b、c);新しいスレッド(pa).start(); thread.sleep(100); //必ず新しいスレッド(pb).start()を実行してください。 thread.sleep(100); }}出力結果:
abcabcabcabcabcabcabcabcabcabcabcabcabcabc
まず、全体的なアイデアを説明しましょう。一般的な観点から、この問題は、3つのスレッド間の同期ウェイクアップ操作です。主な目的は、threada-> threadb-> threadc-> threadaループで3つのスレッドを実行することです。スレッド実行の順序を制御するには、ウェイクアップと待機の順序を決定する必要があるため、各スレッドは実行を継続する前に2つのオブジェクトロックを同時に保持する必要があります。オブジェクトロックは前であり、これは前のスレッドが保持するオブジェクトロックです。もう1つはオブジェクトのロックです。主なアイデアは、実行の順序を制御するために、最初に前のロックを保持する必要があることです。つまり、以前のスレッドは独自のオブジェクトロックをリリースし、次に独自のオブジェクトロックを適用する必要があります。両方が両方であるときに印刷します。次に、最初にself.notify()を呼び出して独自のオブジェクトロックを放出し、次の待機スレッドを起動し、prev.wait()を呼び出してprevオブジェクトロックを解放し、現在のスレッドを終了し、ループが再び目覚めるのを待ちます。上記のコードを実行すると、3つのスレッドがABCをループに印刷し、合計10回表示できます。プログラム実行の主なプロセスは、スレッドAが最初に実行され、CとAのオブジェクトロックを保持し、AとCのロックを放出することです。スレッドBはロックAを待機し、ロックBを使用し、次にBを印刷し、次にB、A A Lock、Wakes C、Wakes C、Thread CがロックBを待っています。問題ですが、慎重に考えると、問題があることがわかります。これが初期条件です。 3つのスレッドは、以前の考えによると、A、B、およびCの順序で開始されます。AはB、B、BはC、Cを目覚め、次に目覚めます。ただし、この仮定は、JVMのスレッドスケジューリングと実行の順序に依存します。
待機と睡眠の違い
一般的な点:
1.それらはすべてマルチスレッド環境にあり、プログラムコールとリターンで指定されたミリ秒数をブロックできます。
2。WAIT()とSleep()の両方が、nuttre()メソッドを介してスレッドの一時停止状態を中断することができるため、スレッドはすぐに割り込みexceptionをスローします。
スレッドAがスレッドBをすぐに終了したい場合、スレッドBに対応するスレッドインスタンスで割り込みメソッドを呼び出すことができます。この瞬間にスレッドBが待機/スリープ/結合している場合、スレッドBはすぐに中断拡張をスローし、catch(){}を直接返してスレッドを安全に終了します。
割り込みのあるエクセプトは、割り込み()メソッドではなく、内側からスレッド自体によってスローされることに注意する必要があります。スレッドで割り込み()が呼び出された場合、スレッドが通常のコードを実行している場合、スレッドは邪魔をしないExceptionをまったくスローしません。ただし、スレッドがwait()/sleep()/join()に入ると、中断exceptionがすぐにスローされます。
違い:
1。スレッドクラスメソッド:sleep()、hieds()など。
オブジェクトメソッド:wait()とnotify()など。
2。各オブジェクトには、同期アクセスを制御するロックがあります。同期されたキーワードは、オブジェクトのロックと対話して、スレッドの同期を実現できます。
睡眠方法はロックを放出しませんが、待機方法はロックを解放し、他のスレッドが同期制御ブロックまたはメソッドを使用できるようにします。
3.待機、Notify、NotifyAllは、同期制御方法または同期制御ブロックでのみ使用できますが、睡眠はどこでも使用できます。
4.睡眠は例外をキャッチする必要があります。待機中、通知、および通知は例外をキャッチする必要はありません。
睡眠()が眠るときは、オブジェクトをロックしてください。
wait()が眠ると、オブジェクトロックが解放されます。
ただし、wait()とsleep()は両方とも、nuttred()メソッドを介してスレッドの一時停止状態を中断するため、スレッドはすぐに中断exceptionをスローすることができます(ただし、この方法を使用することはお勧めしません)。
Sleep()メソッド
Sleep()により、現在のスレッドが停滞状態に入り(現在のスレッドをブロックします)、CUPの使用を放棄します。目的は、現在のスレッドがプロセスだけで得られたCPUリソースを占めるのを防ぎ、他のスレッドが実行するために特定の時間を残すことです。
Sleep()は、スレッドクラスの静的方法です。したがって、オブジェクトのマシンロックを変更することはできないため、スレッドは休眠状態であるが、スレッド()メソッドを同期ブロックで呼び出すと、オブジェクトのマシンロックはリリースされず、他のスレッドはオブジェクトにアクセスできません(ただし、眠っている場合でもオブジェクトロックを保持しています)。
Sleep()Sleep()Sleep Timeが期限切れになった後、他のスレッドが実行されており、スレッドの優先度が高い場合を除き、実行を放棄するようにスケジュールされていないため、スレッドは必ずしもすぐに実行されません。
wait()メソッド
wait()メソッドは、オブジェクトクラスのメソッドです。スレッドがwait()メソッドを実行すると、オブジェクトに関連する待機プールに入り、同時にオブジェクトのマシンロックを失います(リリース)します(一時的にマシンロックを失い、待機(長いタイムアウト)タイムアウト時間もオブジェクトロックを返す必要があります)。他のスレッドはそれにアクセスできます。
wait()は、通知または通知または指定されたスリープ時間を使用して、現在の待機プールのスレッドを起動します。
Wiat()は同期ブロックに配置する必要があります。そうしないと、「java.lang.illegalmonitorStateException」例外がプログラムの実行時にスローされます。
7.一般的なスレッド用語説明
メインスレッド:JVMコールプログラムMain()によって生成されたスレッド。
現在のスレッド:これは紛らわしい概念です。通常、thread.currentthread()を介して取得されたプロセスを指します。
バックグラウンドスレッド:デーモンスレッドとも呼ばれる他のスレッドにサービスを提供するスレッドを指します。 JVMガベージコレクションスレッドは、背景スレッドです。ユーザースレッドとデーモンスレッドの違いは、メインスレッドがメインスレッドの端に応じて前景スレッドを終了するのを待つかどうかです。これは、背景スレッドのサービスを受け入れるスレッドを指します。実際、人形と舞台裏のマニピュレーターとの関係と同じように、前景の背景糸は一緒に接続されています。人形は前景の糸であり、舞台裏のマニピュレーターは背景糸です。フォアグラウンドのスレッドによって作成されたスレッドも、デフォルトでは前景のスレッドです。 isdaemon()およびsetDaemon()メソッドを使用して、スレッドがバックグラウンドスレッドであるかどうかを判断して設定できます。
スレッドクラスのいくつかの一般的な方法:
sleep():swetrをnミリ秒で眠らせます。
ISALIVE():スレッドが生き残るかどうかを判断します。
ing():スレッドが終了するのを待ちます。
ActiveCount():プログラム内のアクティブスレッドの数。
Enumerate():プログラム内のスレッドを列挙します。
currentThread():現在のスレッドを取得します。
isdaemon():スレッドがデーモンスレッドであるかどうか。
SetDaemon():スレッドをデーモンスレッドとして設定します。 (ユーザースレッドとデーモンスレッドの違いは、メインスレッドの端に応じてメインスレッドが終了するのを待つかどうかです)
setName():スレッドの名前を設定します。
wait():スレッドに待機するように強制します。
notify():スレッドに通知して、実行を続行します。
SetPriority():スレッドの優先度を設定します。
8。スレッドの同期
1.同期されたキーワードには2つのスコープがあります。
1)オブジェクトインスタンス内にあります。同期AMETHOD(){}は、複数のスレッドがこのオブジェクトの同期メソッドに同時にアクセスするのを防ぐことができます(オブジェクトに1つのスレッドが同期されたメソッドのいずれかにアクセスする限り、他のスレッドが同時にオブジェクトの同期メソッドにアクセスできない限り)。現時点では、異なるオブジェクトインスタンスの同期された方法は途切れることができません。つまり、他のスレッドは、同じクラスの別のオブジェクトインスタンスの同期メソッドに同時にアクセスできます。
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
Java's support and synchronization mechanism for multithreading is very popular. It seems that using the synchronized keyword can easily solve the problem of multithreaded shared data synchronization.正確に何? It is also necessary to have an in-depth understanding of the role of synchronized keywords before you can make a conclusion.
总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
接着来讨论synchronized用到不同地方对代码产生的影响:
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。
1. 把synchronized当作函数修饰符时,示例代码如下:
Public synchronized void methodAAA(){//….}这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码:
public void methodAAA(){synchronized (this) // (1){ //…..}}(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
2.同步块,示例代码如下:
public void method3(SomeObject so) { synchronized(so){ //…..}}这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable{ private byte[] lock = new byte[0]; // 特殊的instance变量Public void methodA(){ synchronized(lock) { //… }}//…..}注:零长度的byte数组对象创建起来将比任何对象都经济查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
Class Foo{public synchronized static void methodAAA() // 同步的static 函数{//….}public void methodBBB(){ synchronized(Foo.class) // class literal(类名称字面常量)} }代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将Foo.class和P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
九、线程数据传递
在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。
9.1、通过构造方法传递数据在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:
package mythread; public class MyThread1 extends Thread { private String name; public MyThread1(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { Thread thread = new MyThread1("world"); thread.start(); } }由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。
9.2、通过变量和方法传递数据
向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置name变量:
package mythread; public class MyThread2 implements Runnable { private String name; public void setName(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { MyThread2 myThread = new MyThread2(); myThread.setName("world"); Thread thread = new Thread(myThread); thread.start(); }} 9.3、通过回调函数传递数据
上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个value是无法事先就传入线程类的。
package mythread; class Data { public int value = 0; } class Work { public void process(Data data, Integer numbers) { for (int n : numbers) { data.value += n; } } } public class MyThread3 extends Thread { private Work work; public MyThread3(Work work) { this.work = work; } public void run() { java.util.Random random = new java.util.Random(); Data data = new Data(); int n1 = random.nextInt(1000); int n2 = random.nextInt(2000); int n3 = random.nextInt(3000); work.process(data, n1, n2, n3); // 使用回调函数System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" + String.valueOf(n3) + "=" + data.value); } public static void main(String[] args) { Thread thread = new MyThread3(new Work()); thread.start(); } }以上就是对Java 多线程的详解,希望能帮助你学习这部分知识,谢谢大家对本站的支持!