synchronizedの使用と原理、あなたはすべて知っていますか?
synchronizedはJavaが提供する同時制御用のキーワードであり、通常のプログラム開発過程で同時制御を行う際によく使用されるが、不適切な使用があり、同時制御を制御していないだけでなく、システムの問題を引き起こすことがあり、特に合併に対する要求が高いシーンでは事故を引き起こすことがある.
そのため,synchronizedを系統的に学習し,関連知識を整理する必要があり,開発過程で曲がりくねった道を少なくし,より優雅なコードを書くことができる.
本文は主に2つの方面の分かち合いを行います: synchronizedキーワードの使用シーン synchronizedキーワードの最下位の動作メカニズムはどのような ですか.
シーンの操作
Javaでは、オブジェクトごとにMonitorというロックがあります.
synchronized修飾方法
コードの例1を見てみましょう.
スレッドt 1がtestオブジェクトのsynchronizedキーワード修飾のメソッドm 1にアクセスすると、そのオブジェクトはロックされ、このメソッドにアクセスする別のスレッドt 2があるとブロックされ、Aスレッドがm 1を実行するまでロックが解除され、m 1メソッドにアクセスして実行される.
上記のコードはsynchronizedが使用する場合の1つにすぎませんが、実際には2つに分けることができます.
一つは普通の方法を修飾することです
1つは静的を修飾する方法です
オブジェクトにはロックされますが、ロックされているオブジェクトは異なります.
上記のコードは、synchronizedは、現在のメソッドが存在するクラスのオブジェクトインスタンスにロックされ、静的メソッドsynchronizedを修飾すると、現在のメソッドが存在するクラスのClassオブジェクトにロックされます.
次のコード例2を見てみましょう.
スレッドt 1がtestオブジェクトのm 1にアクセスすると、現在のtestオブジェクトのインスタンスがロックされ、スレッドt 2がtestオブジェクトのm 2にアクセスすると、現在のオブジェクトのclassオブジェクトがロックされるので、2つのスレッドが前後して起動すると、t 1が先にm 1を実行すると仮定するが、t 2アクセスオブジェクトのロックターゲットとt 1のロックが異なるため、t 2はt 1の実行によって3秒間休止することはない、すぐに実行されます.
したがって、1つのクラスで、1つのスレッドがsynchronizedキーワードで修飾された一般的なメソッドにアクセスし、1つのスレッドがsynchronizedキーワードで修飾された静的メソッドにアクセスすると、2つのオブジェクトのロックのターゲットは異なるため、互いに影響しません.
また、1つのクラスにsynchronizedキーワードで修飾された2つの一般的なメソッドがあり、1つのスレッドが1つにアクセスすると、もう1つのアクセスが同じオブジェクトインスタンスロックを持つため、別のスレッドもブロックされることに注意してください.synchronizedキーワードで修飾された静的メソッドも同様です.
synchronized修飾コードブロック
synchronizedキーワードは、synchronizedキーワードが方法を修飾できるほか、synchronizedキーワードはコードブロックを修飾することもできる.
コード例3を見てみましょう.
スレッドt 1がm 1にアクセスすると、現在のオブジェクトインスタンスがロックされ、スレッドt 2がm 2にアクセスすると、現在のオブジェクトのclassオブジェクトがロックされるため、2つのスレッドが実行されると互いに影響を受けてブロックされることはない.ここで具体的なロックの制御と修飾方法は類似している.
両者の異同点
同じ
どちらも通常のオブジェクトインスタンスにロックとクラスのclassオブジェクトにロックをかけることができます.
区別する
1.修飾方法は粗粒度の同時制御であり、ある時点でsynchronizedによって修飾された方法に1つのスレッドしかアクセスできない.修飾コードブロックはより細粒度の同時制御であり、方法の局所コードのみを同時制御することができ、方法中のコードブロック外のコードは複数のスレッドで同時に実行することができる.
2.修飾コードブロックは修飾方法の同時性能より高い.
ていそうげんり
メソッドとコードブロックにおけるsynchronized修飾子の応用について説明したが,これは使用レベルの内容にすぎない.メソッドやコードブロックに追加すると、コンカレント制御が可能になり、コンカレント制御が簡単に見えます.そのことを知っていてもその理由を知っているという意志に基づいて、底でどうやってやってやったのかを知る必要があります.底の原理を見てみましょう.
synchronized修飾子はJavaのJDKが提供するキーワードであり、最下位ではJVMによる同時制御が実現される.JavaオブジェクトごとにMonitorロックがあると述べたように、synchronized修飾子がコンパイラによってコンパイルされた後、バイトコードレベルでどのようなものかを見てみましょう.
例2を例にとる
コンパイラがコンパイルしたclassファイルがあるディレクトリを見つけ、jdkが提供するコマンドjavap-v Testで逆コンパイルし、次のバイトコードを得る
上のバイトコードから分かるように、
1つは、synchronized修飾を通常の方法で追加することです.
flagsにACC_が現れましたSYNCRONIZED,スレッドt 1がメソッドm 1にアクセスすると,flagにこのACC_があることが分かった.SYNCRONIZEDフラグは、現在のオブジェクトインスタンスに対してロックされ、t 2スレッドがメソッドm 1にアクセスするとflagsにこのフラグがあるかどうかをチェックし、ある場合は現在のオブジェクトインスタンスがロックされているかどうかを見て、ロックされている場合はブロック状態に入り、t 1スレッドがロックを解除してから再び入るのを待つ.
1つは静的メソッドにsynchronized修飾を追加することです
flagsにACC_が現れましたSTATIC, ACC_SYNCRONIZED,スレッドt 1がメソッドm 1にアクセスすると,flagはACC_を同時に有することが分かった.STATICフラグとACC_SYNCRONIZEDフラグは、現在のオブジェクトのclassオブジェクトに対してロックをかけます.この場合、t 2スレッドがメソッドm 1にアクセスするとflagsにもこの2つのフラグがあるかどうかをチェックし、ある場合は現在のオブジェクトのclassオブジェクトがロックされているかどうかを確認し、ロックされている場合はブロック状態に入り、t 1スレッドの実行が完了してロックが解除されるのを待ってから、再び入ります.
コード3を見てみましょう.
Javap-v Testで逆コンパイルされたバイトコードは以下の通りです.
上のバイトコードから分かるように、
1つは、synchronized修飾コードブロックを通常のオブジェクトに修飾する
t 1がメソッドm 1を呼び出すと、flagsにACC_があるかどうかをチェックするSYNCRONIZEDロゴとACC_STATICフラグは、実行中にmonitorrenterが存在すると、現在のオブジェクトインスタンスがロックされ、t 2もメソッドを呼び出すと、上記の手順に従って現在のオブジェクトがロックされていることが判明すると、ブロックに入り、t 1の実行が完了してリリースされるのを待って実行に入る.
1つはclassオブジェクトでsynchronized修飾コードブロックを修飾する
t 1がメソッドm 1を呼び出すと、flagsにACC_があるかどうかをチェックするSYNCRONIZEDロゴとACC_STATICマーク、ACCがあればSTATICマーク、ACCなしSYNCRONIZEDは、実行中にmonitorrenterがあることを発見すると、現在のclassオブジェクトインスタンスにロックをかけ、t 2もメソッドを呼び出すと、上記の手順に従って、現在のclassオブジェクトがロックされていることを発見すると、ブロックに入り、t 1の実行が完了してリリースされるのを待ってから実行に入る.
まとめ jvmでの同期は、モニタオブジェクト(Monitor)へのアクセスと終了に基づいて行われる.MonitorはC++で実現され、各オブジェクトインスタンスにはjavaオブジェクトとともに作成および破棄されるmonitorオブジェクトがあります.スレッドがmonitorオブジェクトを取得すると、monitorは下位オペレーティングシステムのmutex lockに依存して反発を実現し、同時制御を可能にする. synchronizedメソッドでは、flagsのACC_を介して使用されます.STATICおよびmonitorenter、monitorexitは、ロックおよび解放ロック を制御する synchronizedはコードブロックに用いられ、flagsを通過するACC_STATIC, ACC_SYNCRONIZEDの2つのフラグビットは、ロックおよびリリースロック を制御する.
そのため,synchronizedを系統的に学習し,関連知識を整理する必要があり,開発過程で曲がりくねった道を少なくし,より優雅なコードを書くことができる.
本文は主に2つの方面の分かち合いを行います:
シーンの操作
Javaでは、オブジェクトごとにMonitorというロックがあります.
synchronized修飾方法
コードの例1を見てみましょう.
public class Test {
public static void main(String[] args) throws Exception{
Test test = new Test();
Thread t1 = new Thread(() -> test.m1());
t1.start()
Thread t2 = new Thread(() -> test.m1());
t2.start();
}
public synchronized void m1() {
System.out.println("m1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
スレッドt 1がtestオブジェクトのsynchronizedキーワード修飾のメソッドm 1にアクセスすると、そのオブジェクトはロックされ、このメソッドにアクセスする別のスレッドt 2があるとブロックされ、Aスレッドがm 1を実行するまでロックが解除され、m 1メソッドにアクセスして実行される.
上記のコードはsynchronizedが使用する場合の1つにすぎませんが、実際には2つに分けることができます.
一つは普通の方法を修飾することです
1つは静的を修飾する方法です
オブジェクトにはロックされますが、ロックされているオブジェクトは異なります.
上記のコードは、synchronizedは、現在のメソッドが存在するクラスのオブジェクトインスタンスにロックされ、静的メソッドsynchronizedを修飾すると、現在のメソッドが存在するクラスのClassオブジェクトにロックされます.
次のコード例2を見てみましょう.
public class Test {
public static void main(String[] args) throws Exception{
Test test = new Test();
Thread t1 = new Thread(() -> test.m1());
t1.start()
Thread t2 = new Thread(() -> test.m1());
t2.start();
}
public synchronized void m1() {
System.out.println("m1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static synchronized void m2() {
System.out.println("m2");
}
}
スレッドt 1がtestオブジェクトのm 1にアクセスすると、現在のtestオブジェクトのインスタンスがロックされ、スレッドt 2がtestオブジェクトのm 2にアクセスすると、現在のオブジェクトのclassオブジェクトがロックされるので、2つのスレッドが前後して起動すると、t 1が先にm 1を実行すると仮定するが、t 2アクセスオブジェクトのロックターゲットとt 1のロックが異なるため、t 2はt 1の実行によって3秒間休止することはない、すぐに実行されます.
したがって、1つのクラスで、1つのスレッドがsynchronizedキーワードで修飾された一般的なメソッドにアクセスし、1つのスレッドがsynchronizedキーワードで修飾された静的メソッドにアクセスすると、2つのオブジェクトのロックのターゲットは異なるため、互いに影響しません.
また、1つのクラスにsynchronizedキーワードで修飾された2つの一般的なメソッドがあり、1つのスレッドが1つにアクセスすると、もう1つのアクセスが同じオブジェクトインスタンスロックを持つため、別のスレッドもブロックされることに注意してください.synchronizedキーワードで修飾された静的メソッドも同様です.
synchronized修飾コードブロック
synchronizedキーワードは、synchronizedキーワードが方法を修飾できるほか、synchronizedキーワードはコードブロックを修飾することもできる.
コード例3を見てみましょう.
public class Test {
public static void main(String[] args) throws Exception{
Test test = new Test();
Thread t1 = new Thread(() -> test.m1());
t1.start()
Thread t2 = new Thread(() -> test.m1());
t2.start();
}
public void m1() {
synchronized(this){
System.out.println("m1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void m2() {
synchronized(Test.class) {
System.out.println("m2");
}
}
}
スレッドt 1がm 1にアクセスすると、現在のオブジェクトインスタンスがロックされ、スレッドt 2がm 2にアクセスすると、現在のオブジェクトのclassオブジェクトがロックされるため、2つのスレッドが実行されると互いに影響を受けてブロックされることはない.ここで具体的なロックの制御と修飾方法は類似している.
両者の異同点
同じ
どちらも通常のオブジェクトインスタンスにロックとクラスのclassオブジェクトにロックをかけることができます.
区別する
1.修飾方法は粗粒度の同時制御であり、ある時点でsynchronizedによって修飾された方法に1つのスレッドしかアクセスできない.修飾コードブロックはより細粒度の同時制御であり、方法の局所コードのみを同時制御することができ、方法中のコードブロック外のコードは複数のスレッドで同時に実行することができる.
2.修飾コードブロックは修飾方法の同時性能より高い.
ていそうげんり
メソッドとコードブロックにおけるsynchronized修飾子の応用について説明したが,これは使用レベルの内容にすぎない.メソッドやコードブロックに追加すると、コンカレント制御が可能になり、コンカレント制御が簡単に見えます.そのことを知っていてもその理由を知っているという意志に基づいて、底でどうやってやってやったのかを知る必要があります.底の原理を見てみましょう.
synchronized修飾子はJavaのJDKが提供するキーワードであり、最下位ではJVMによる同時制御が実現される.JavaオブジェクトごとにMonitorロックがあると述べたように、synchronized修飾子がコンパイラによってコンパイルされた後、バイトコードレベルでどのようなものかを見てみましょう.
例2を例にとる
コンパイラがコンパイルしたclassファイルがあるディレクトリを見つけ、jdkが提供するコマンドjavap-v Testで逆コンパイルし、次のバイトコードを得る
public synchronized void m1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=2, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String m1
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: ldc2_w #5 // long 3000l
11: invokestatic #7 // Method java/lang/Thread.sleep:(J)V
14: goto 22
17: astore_1
18: aload_1
19: invokevirtual #9 // Method java/lang/InterruptedException.printStackTrace:()V
22: return
public static synchronized void m2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #10 // String m2
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
上のバイトコードから分かるように、
1つは、synchronized修飾を通常の方法で追加することです.
flagsにACC_が現れましたSYNCRONIZED,スレッドt 1がメソッドm 1にアクセスすると,flagにこのACC_があることが分かった.SYNCRONIZEDフラグは、現在のオブジェクトインスタンスに対してロックされ、t 2スレッドがメソッドm 1にアクセスするとflagsにこのフラグがあるかどうかをチェックし、ある場合は現在のオブジェクトインスタンスがロックされているかどうかを見て、ロックされている場合はブロック状態に入り、t 1スレッドがロックを解除してから再び入るのを待つ.
1つは静的メソッドにsynchronized修飾を追加することです
flagsにACC_が現れましたSTATIC, ACC_SYNCRONIZED,スレッドt 1がメソッドm 1にアクセスすると,flagはACC_を同時に有することが分かった.STATICフラグとACC_SYNCRONIZEDフラグは、現在のオブジェクトのclassオブジェクトに対してロックをかけます.この場合、t 2スレッドがメソッドm 1にアクセスするとflagsにもこの2つのフラグがあるかどうかをチェックし、ある場合は現在のオブジェクトのclassオブジェクトがロックされているかどうかを確認し、ロックされている場合はブロック状態に入り、t 1スレッドの実行が完了してロックが解除されるのを待ってから、再び入ります.
コード3を見てみましょう.
Javap-v Testで逆コンパイルされたバイトコードは以下の通りです.
public void m1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String m1
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: ldc2_w #5 // long 3000l
15: invokestatic #7 // Method java/lang/Thread.sleep:(J)V
18: goto 26
21: astore_2
22: aload_2
23: invokevirtual #9 // Method java/lang/InterruptedException.printStackTrace:()V
26: aload_1
27: monitorexit
28: goto 36
31: astore_3
32: aload_1
33: monitorexit
34: aload_3
35: athrow
36: return
public static void m2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: ldc #10 // class Test
2: dup
3: astore_0
4: monitorenter
5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #11 // String m2
10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_0
14: monitorexit
15: goto 23
18: astore_1
19: aload_0
20: monitorexit
21: aload_1
22: athrow
23: return
上のバイトコードから分かるように、
1つは、synchronized修飾コードブロックを通常のオブジェクトに修飾する
t 1がメソッドm 1を呼び出すと、flagsにACC_があるかどうかをチェックするSYNCRONIZEDロゴとACC_STATICフラグは、実行中にmonitorrenterが存在すると、現在のオブジェクトインスタンスがロックされ、t 2もメソッドを呼び出すと、上記の手順に従って現在のオブジェクトがロックされていることが判明すると、ブロックに入り、t 1の実行が完了してリリースされるのを待って実行に入る.
1つはclassオブジェクトでsynchronized修飾コードブロックを修飾する
t 1がメソッドm 1を呼び出すと、flagsにACC_があるかどうかをチェックするSYNCRONIZEDロゴとACC_STATICマーク、ACCがあればSTATICマーク、ACCなしSYNCRONIZEDは、実行中にmonitorrenterがあることを発見すると、現在のclassオブジェクトインスタンスにロックをかけ、t 2もメソッドを呼び出すと、上記の手順に従って、現在のclassオブジェクトがロックされていることを発見すると、ブロックに入り、t 1の実行が完了してリリースされるのを待ってから実行に入る.
まとめ