synchronizedの使用と原理、あなたはすべて知っていますか?


synchronizedはJavaが提供する同時制御用のキーワードであり、通常のプログラム開発過程で同時制御を行う際によく使用されるが、不適切な使用があり、同時制御を制御していないだけでなく、システムの問題を引き起こすことがあり、特に合併に対する要求が高いシーンでは事故を引き起こすことがある.
そのため,synchronizedを系統的に学習し,関連知識を整理する必要があり,開発過程で曲がりくねった道を少なくし,より優雅なコードを書くことができる.
本文は主に2つの方面の分かち合いを行います:
  • synchronizedキーワードの使用シーン
  • synchronizedキーワードの最下位の動作メカニズムはどのような
  • ですか.
    シーンの操作
    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の実行が完了してリリースされるのを待ってから実行に入る.
    まとめ
  • jvmでの同期は、モニタオブジェクト(Monitor)へのアクセスと終了に基づいて行われる.MonitorはC++で実現され、各オブジェクトインスタンスにはjavaオブジェクトとともに作成および破棄されるmonitorオブジェクトがあります.スレッドがmonitorオブジェクトを取得すると、monitorは下位オペレーティングシステムのmutex lockに依存して反発を実現し、同時制御を可能にする.
  • synchronizedメソッドでは、flagsのACC_を介して使用されます.STATICおよびmonitorenter、monitorexitは、ロックおよび解放ロック
  • を制御する
  • synchronizedはコードブロックに用いられ、flagsを通過するACC_STATIC, ACC_SYNCRONIZEDの2つのフラグビットは、ロックおよびリリースロック
  • を制御する.