並行プログラミングに関する注意事項2_同期使用と注意事項

序文:

前回の記事では、同時実行性のバグの原因について学びました。あるスレッドのタイムスライスが使い果たされると、オペレーティングシステムが別のスレッドに切り替わることがわかっています。これら2つのスレッドが同じリソースにアクセスすると、同時実行性の問題が発生する可能性があります。 。
この共有リソースは一度に1つのスレッドしかアクセスできず、他のスレッドはアクセスできない場合、スレッドの切り替えによる問題はないと考えられます。Javaコンカレントプログラミングは、このようなメカニズム、ミューテックスロックを提供して、一度に1つのスレッドのみが共有リソースにアクセスできるようにします。
Javaには同期とロックがあります。今日は、まず同期キーワードを見てみましょう。

手順:

Synchroizedは、変更方法とコードブロックに分かれています:
変更方法:

// 修饰方法  锁为对象
public synchronized void method1() {
    
    
       // 处理过程
   }

// 修饰静态方法 锁是类
public synchronized static void method2() {
    
    
       // 处理过程
   }

変更されたコードブロック:

// 修饰代码块 锁为对象
public void method3() {
    
    
      synchronized (this) {
    
    
          // 处理过程
      }
  }

// 修饰代码块 锁为类
  public void method4() {
    
    
      synchronized (Test.class) {
    
    
          // 处理过程
      }
  }

共有変数をスレッドに追加する典型的な例を見てみましょう

public class Test {
    
    

    private int i = 0;

    public void add() {
    
      // (1)
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        Test test = new Test();
        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 10000; i++) {
    
    
                test.add();
            }
        });
        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 10000; i++) {
    
    
                test.add();
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("i的值是" + test.i); // (2) i的值是18084
    }
}

期待される結果は20000で、計算値は20000未満です。スレッドt1とスレッドt2はほぼ同時に開始され、それぞれがiの値を独自のキャッシュに読み込みます。1つのスレッドタイムスライスが使い果たされるため、オペレーティングシステムが切り替わります。別のスレッド
にとって、この時点ではスレッドt1のキャッシュ内の値はメモリにリフレッシュされておらず、スレッドt2からiへの増加はそれ自体のキャッシュ内の値であり、t2の増加後、iの値はメモリにフラッシュされます。t1を追加する操作は、実行しないことと同じであるため、iの最終値は20000未満になります。

synchroizedキーワードが(1)で追加されると、次のようになります。public
synchronized void add(){ スレッドt1がadd()関数を実行すると、最初に重要な領域(つまり、同期によって変更される部分)に入り、モニターを取得します。ロック。この時点でスレッド切り替えが実行された場合、別のスレッドはロックを取得していないため、タイムスライスが使い果たされるとブロックされます。スレッドがt1に切り替えられると、実行を継続できます。1つのスレッドのみが同時に動作することを許可するこのようなmutexは、共有変数に問題がないことを保証します。

再入可能ロック

再入可能性:スレッドがロックを取得すると、ブロックせずに繰り返しロックを取得できます。たとえば、次の例では、スレッド1がadd1を呼び出してロックを取得し、その後add2を呼び出した後にロックを取得した場合、スレッド1は通常どおり実行できます。これは再入可能ロックです
。Javaの他のロックReentrantLock(再入可能ロックも同じ効果)

public synchronized void add1() {
    
    
              add()2;
          }

public synchronized void add2() {
    
    
            // 处理内容
        }

同期の実現

次のTestクラスはjavac Test.javaでコンパイルされ、クラスファイルはjavap -v Testで逆コンパイルされて、追加情報が表示されます。

public class Test {
    
    
    public synchronized void method1(){
    
    
    }

    public void method2(){
    
    
        synchronized (Test.class){
    
    
        }
    }
}

情報を見る

public synchronized void method1();
   descriptor: ()V
   flags: ACC_PUBLIC, ACC_SYNCHRONIZED
   Code:
     stack=0, locals=1, args_size=1
        0: return
     LineNumberTable:
       line 12: 0

 public void method2();
   descriptor: ()V
   flags: ACC_PUBLIC
   Code:
     stack=2, locals=3, args_size=1
        0: ldc           #2                  // class com/algorithm/leetcode/leetcode/Test
        2: dup
        3: astore_1
        4: monitorenter
        5: aload_1
        6: monitorexit
        7: goto          15
       10: astore_2
       11: aload_1
       12: monitorexit
       13: aload_2
       14: athrow
       15: return

javaの同期メソッドはACC_SYNCHRONIZEDによって制御され、同期コードブロックはmonitorenterとmonitorexitによって制御されることがわかります。monitorenterはクリティカルゾーンに入るという意味で、monitorexitはクリティカルゾーンから出るという意味です。
同期はモニターオブジェクトによって制御されます。任意のJavaオブジェクトは、ロックを所有するスレッドの情報を格納するモニターオブジェクト(単純に同期ロックと理解できます)になることができます。この情報から、どのスレッドが保持しているかを知ることができます。このロックがあります。
jvm内のオブジェクトは、インスタンス変数、オブジェクトヘッダー、入力データなどで構成され、モニターはオブジェクトヘッダーに格納されます。オブジェクトヘッダーのMarkWord内のLockWordはモニターの開始アドレスを指し示すため、どのスレッドが保持しているかがわかります。ロック。

予防:

以下は、Javaコンカレントプログラミングの実践コースのトピックです。


class SafeCalc {
    
    
  long value = 0L;
  long get() {
    
    
    synchronized (new Object()) {
    
    
      return value;
    }
  }
  void addOne() {
    
    
    synchronized (new Object()) {
    
    
      value += 1;
    }
  }
}

この方法で同期を使用しても効果はありません。上記の内容を読んだ後、ロックのアドレスがオブジェクトヘッダーに格納されていることがわかります。2つのオブジェクトが新しい場合、2つの異なるロックがあるため、機能しません。

参考資料:

1.https://time.geekbang.org/column/intro/159オタク時間の並行プログラミングの練習
2.「Java並行プログラミングの練習」Doug lea
3.http://www.hollischuang.com/archives/1883 Synchronized実装の原則
4.https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10
5.https://blog.csdn.net/sc9018181134/article / details / 80643360(klass object my other blog)

おすすめ

転載: blog.csdn.net/sc9018181134/article/details/103230413