まず、同期の詳細な説明を読みましょう。
同期は、Java言語のキーワードです。メソッドまたはコードブロックを変更するために使用されると、最大で1つのスレッドが同時にコードを実行することを保証できます。
1. 2つの同時スレッドが同じオブジェクトオブジェクトでこの同期(この)同期コードブロックにアクセスすると、1回以内に1つのスレッドのみを実行できます。別のスレッドは、コードブロックを実行する前に、現在のスレッドがこのコードブロックを実行するのを待つ必要があります。
2.ただし、1つのスレッドがオブジェクトの同期(この)同期コードブロックにアクセスすると、別のスレッドはそのオブジェクトの非同期(この)同期コードブロックにアクセスできます。
3.スレッドがオブジェクトの同期(この)同期コードブロックにアクセスすると、他のスレッドがオブジェクト内の他のすべての同期(この)同期コードブロックにアクセスするのをブロックすることが特に重要です。
4. 3番目の例は、他の同期コードブロックにも適用されます。つまり、スレッドがオブジェクトの同期(この)同期コードブロックにアクセスすると、このオブジェクトのオブジェクトロックが取得されます。その結果、オブジェクトオブジェクトのすべての同期コード部分への他のスレッドアクセスが一時的にブロックされます。
5.上記のルールは、他のオブジェクトロックにも適用されます。
簡単に言えば、Synchronizedは現在のスレッドのロックを宣言します。このロックを備えたスレッドは、ブロック内の命令を実行でき、他のスレッドは同じ操作の前にロックが取得するのを待つことのみです。
これは非常に便利ですが、私は別の奇妙な状況に遭遇しました。
1。同じクラスには、同期されたキーワード宣言の使用:2つの方法があります。
2。メソッドの1つを実行する場合、他のメソッド(非同期スレッドコールバック)が実行されるのを待つ必要があるため、CountDownLatchを使用して待機します。
3.コードは次のように分解されます。
同期void a(){countDownLatch = new CountDownLatch(1); // someing countdownlatch.await();} synchronized void b(){countdownlatch.countdown();}で
方法Aはメインスレッドによって実行され、方法Bは非同期スレッドによって実行され、コールバック実行結果は次のとおりです。
メインスレッドは、Aメソッドを実行した後に立ち往生し始めますが、もはやそれを行いません。
これは古典的なデッドロックの問題です
AはBが実行されるのを待ちますが、実際には、Bがコールバックであるとは思わないでください。BはAが実行されるのを待っています。なぜ?同期された役割を果たします。
一般的に言えば、コードのブロックを同期したい場合は、共有変数を使用してロックする必要があります。
byte [] mutex = new byte [0]; void a1(){synchronized(mutex){// dosomething}} void b1(){synchronized(mutex){// dosomething}}}} AメソッドとBメソッドの内容がそれぞれA1メソッドとB1メソッドの同期ブロックに移行された場合、理解しやすくなります。
A1が実行された後、(CountDownLatch)B1メソッドが実行されるのを間接的に待ちます。
ただし、A1のミューテックスはリリースされていないため、B1を待ち始めます。現時点では、非同期コールバックB1メソッドがロックを解放するのを待つ必要がある場合でも、Bメソッドは実行されません。
これはデッドロックを引き起こしました!
ここの同期キーワードはメソッドの前に配置され、関数は同じです。 Java言語がMutexの宣言と使用を隠すのに役立つというだけです。同じオブジェクトで使用される同期されたメソッドは同じであるため、非同期コールバックでさえデッドロックを引き起こすため、この問題に注意してください。このレベルのエラーは、同期されたキーワードが不適切に使用されることです。ランダムに使用しないでください。正しく使用してください。
それでは、そのような目に見えないMutexオブジェクトは何ですか?
例自体は考えやすいです。このようにして、新しいオブジェクトを定義してロックを作成する必要はないからです。このアイデアを証明するために、それを証明するためのプログラムを書くことができます。
アイデアはとてもシンプルです。クラスを定義すると、2つの方法があります。 1つは同期され、もう1つはメソッド本体で同期(これ)が使用されます。次に、2つのスレッドを起動して、これら2つのメソッドを個別に呼び出します。 2つの方法(待機)の間でロック競合が発生した場合、メソッドによって宣言された同期の目に見えない変異が実際にインスタンス自体であることを説明できます。
Public Class MultiThreadSync {public同期Void M1()Throws arturtedexception {system。 out.println( "m1 call");糸。睡眠(2000);システム。 out.println( "m1 call done"); } public void m2()throws arturtedexception {synchronized(this){system。 out.println( "m2 call");糸。睡眠(2000);システム。 out.println( "m2 call done"); }} public static void main(string [] args){final multithreadsync thisobj = new multithreadsync();スレッドT1 = newスレッド(){@Override public void run(){try {thisobj.m1(); } catch(arturnedexception e){e.printstacktrace(); }}}};スレッドT2 = new Sthrea(){@Override public void run(){try {thisobj.m2(); } catch(arturnedexception e){e.printstacktrace(); }}}; t1.start(); t2.start(); }}結果出力は次のとおりです。
M1 CALLM1 CALL DONEM2 CALLM2 CALL CALLが完了しました
メソッドM2の同期ブロックがM1の実行を待っていることが説明されています。これにより、上記の概念を確認できます。
同期が静的メソッドに追加されると、クラスレベルの方法であるため、ロックされたオブジェクトは現在のクラスのクラスインスタンスであることに注意してください。また、プログラムを作成して証明することもできます。ここでは省略されています。
したがって、メソッドの同期されたキーワードは、読んでいるときに同期(this){}に自動的に置き換えることができますが、これは理解しやすいです。
void method(){void synchronized method(){synchronized(this){// biz code // biz code} ------ >>>}}同期からのメモリの可視性
Javaでは、同期したキーワードを使用してスレッド間の相互排除を実装できることは誰もが知っていますが、メモリ内の変数の可視性を確保するために、つまり、2つのスレッドが同じ変数に読み取りおよび書き込みアクセスを同時に読み取ると、同期されたスレッドが最新のスレッドが更新されることを保証するために使用されます。
たとえば、次の例:
パブリッククラスのNovisibility {private static boolean Ready = false; private static int number = 0; private static class readerthread extends thread {@override public void run(){while(!reading){shood.yield(); // CPUをサポートして、他のスレッドを機能させる} system.out.println(number); }} public static void main(string [] args){new ReaderThread()。start();番号= 42; Ready = true; }}スレッドの読み取りは何を出力すると思いますか? 42?通常の状況では、42が出力されます。ただし、問題の並べ替えにより、読み取りスレッドは0または出力されない場合があります。
コンパイラがJavaコードをBytecodeにコンパイルするときにコードを再注文することができ、CPUはマシンの命令を実行するときに命令を再注文することもできます。並べ替えがプログラムのセマンティクスを破壊しない限り -
単一のスレッドでは、並べ替えがプログラムの実行結果に影響を与えない限り、並べ替えが他のスレッドに大きな影響を与える可能性がある場合でも、その操作をプログラムで指定した順序で実行する必要があることを保証することはできません。
これは、ステートメント「Ready = True」の実行が、ステートメント「番号= 42」の実行よりも優先される可能性があることを意味します。この場合、読み取りスレッドは番号0のデフォルト値を出力する場合があります。
Javaメモリモデルでは、問題を並べ替えると、そのようなメモリの可視性の問題が発生します。 Javaメモリモデルでは、各スレッドには独自の作業メモリ(主にCPUキャッシュまたはレジスタ)があり、変数に対する操作は独自の作業メモリで実行されますが、スレッド間の通信はメインメモリとスレッドワーキングメモリ間の同期によって達成されます。
たとえば、上記の例では、書き込みスレッドは42に正常に更新され、trueに準備ができていますが、書き込みスレッドはメインメモリとのみを同期させる可能性が非常に高い(おそらくCPUの書き込みバッファーのため)、後続の読み取りスレッドによる読み取り値が常に誤っているため、上記のコードは数値値を出力しません。
同期したキーワードを使用して同期すると、そのような問題はありません。
パブリッククラスのNovisibility {private static boolean Ready = false; private static int number = 0; private static object lock = new object(); private static class readerthread extends thread {@override public void run(){synchronized(lock){while(!ready){thread.yield(); } system.out.println(number); }} public static void main(string [] args){synchronized(lock){new readerthread()。start();番号= 42; Ready = true; }}}これは、Javaメモリモデルが同期されたセマンティクスの次の保証を提供するためです。
つまり、ThreadaがロックMを解放すると、書かれた変数(作業メモリに存在するxやyなど)がメインメモリに同期されます。 ThreadBが同じロックMに適用されると、ThreadBのワーキングメモリが無効に設定され、ThreadBはメインメモリからワーキングメモリにアクセスする変数をリロードします(この時点で、x = 1、y = 1はthreadaで変更された最新値です)。このようにして、ThreadaからThreadBへのスレッド間の通信が達成されます。
これは実際には、JSR133によって定義された事前のルールの1つです。 JSR133は、Javaメモリモデルの以前のルールの次のセットを定義しています。
実際、この一連のルールは、操作間のメモリの可視性を定義します。 bの前に操作が発生する場合、操作の実行結果(変数への書き込みなど)がB操作を実行するときに表示する必要があります。
これらのルールをより深く理解するために、以前のルールのために、例を見てみましょう。
//スレッドaおよびbオブジェクトロックで共有されるコード= new object(); int a = 0; int b = 0; int c = 0; //スレッドA、次のコード同期(lock){a = 1; // 1 b = 2; // 2} // 3c = 3; // 4 //スレッドB、次のコードを呼び出します同期(ロック){// 5 System.out.println(a); // 6 System.out.println(b); // 7 System.out.println(c); // 8}スレッドAが最初に実行され、3つの変数a、b、およびcに値を割り当てると仮定します(注:変数a、bの割り当ては同期ステートメントブロックで実行されます)、次にスレッドbが再び実行され、これらの3つの変数の値を読み取り、それらを印刷します。では、スレッドBで印刷された変数A、B、およびCの値は何ですか?
シングルスレッドルールによれば、スレッドAの実行では、1つの操作が2つの操作前に行われ、2つの操作が3つの操作の前に行われ、3つの操作が4つの操作の前に行われることを取得できます。同様に、スレッドBの実行では、5つの操作が6つの操作前に行われ、6つの操作が7操作前に行われ、7つの操作が8操作の前に行われます。モニターのロック解除とロックの原則によれば、3つの操作(ロック解除操作)は、5操作(ロック操作)の前に行われます。推移的なルールによれば、操作1と2は操作6、7、および8の前に行われると結論付けることができます。
操作1および2の実行結果は操作6、7、8に表示されるため、操作bで印刷される必要があるため、操作1および2の実行結果が表示されます。ルールの前に既存の発生に応じて、操作8の前に操作4が発生すると推定することはできません。したがって、スレッドBでは、Cにアクセスされる変数は3ではなく0である場合があります。