ホームページ  >  記事  >  Java  >  Java マルチスレッド同期のいくつかの方法の紹介

Java マルチスレッド同期のいくつかの方法の紹介

黄舟
黄舟オリジナル
2017-09-20 10:04:061566ブラウズ

この記事では、主に Java マルチスレッドの同期のいくつかの方法に関する関連情報を紹介します。必要な方は、

Java マルチスレッドのいくつかの同期方法

を参照してください。

数日前の面接で、マスターからまたたくさんの基礎知識を学ばなければなりませんでした。早速、本題に入りましょう。

2. スレッド同期が必要な理由

複数のスレッドが変数またはオブジェクトに同時にアクセスしている場合、これらのスレッドで読み取り操作と書き込み操作の両方が行われると、変数の値またはオブジェクトの状態が変化し、プログラム例外が発生します。たとえば、銀行口座が 2 つのスレッドで同時に操作されている場合、1 つは 100 元を引き出し、もう 1 つは 100 元を入金します。アカウントのブロックが元々 0 であると仮定します。出金スレッドと入金スレッドが同時に発生した場合はどうなりますか?出金に失敗し、口座残高は 100 です。 出金は成功し、口座残高は 0 です。では、どちらでしょうか?わかりにくいですね。そこで、この問題を解決するのがマルチスレッド同期です。

3. 同期されていない場合のコード

Bank.java


package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private int count =0;//账户余额 

  //存钱 
  public void addMoney(int money){ 
    count +=money; 
    System.out.println(System.currentTimeMillis()+"存进:"+money); 
  } 

  //取钱 
  public void subMoney(int money){ 
    if(count-money < 0){ 
      System.out.println("余额不足"); 
      return; 
    } 
    count -=money; 
    System.out.println(+System.currentTimeMillis()+"取出:"+money); 
  } 

  //查询 
  public void lookMoney(){ 
    System.out.println("账户余额:"+count); 
  } 
}

SyncThreadTest.java


package threadTest; 
/**
 * Java学习交流QQ群:589809992 我们一起学Java!
 */
public class SyncThreadTest { 

  public static void main(String args[]){ 
    final Bank bank=new Bank(); 

    Thread tadd=new Thread(new Runnable() { 

      @Override 
      public void run() { 
        // TODO Auto-generated method stub 
        while(true){ 
          try { 
            Thread.sleep(1000); 
          } catch (InterruptedException e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
          } 
          bank.addMoney(100); 
          bank.lookMoney(); 
          System.out.println("\n"); 

        } 
      } 
    }); 

    Thread tsub = new Thread(new Runnable() { 

      @Override 
      public void run() { 
        // TODO Auto-generated method stub 
        while(true){ 
          bank.subMoney(100); 
          bank.lookMoney(); 
          System.out.println("\n"); 
          try { 
            Thread.sleep(1000); 
          } catch (InterruptedException e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
          }   
        } 
      } 
    }); 
    tsub.start(); 

    tadd.start(); 
  } 

}

コードは非常に簡単なので、説明は省略します。みたいなの?一部を切り取ってみましたが、とてもごちゃごちゃしていて理解できません。


余额不足 
账户余额:0 

余额不足 
账户余额:100 

1441790503354存进:100 
账户余额:100 

1441790504354存进:100 
账户余额:100 

1441790504354取出:100 
账户余额:100 

1441790505355存进:100 
账户余额:100 

1441790505355取出:100 
账户余额:100

4. 同期を使用する場合のコード

(1) 同期方法:

synchronizedキーワードで変更したメソッドがあります。 Java のすべてのオブジェクトには組み込みロックがあるため、このキーワードを使用してメソッドが変更されると、組み込みロックによってメソッド全体が保護されます。このメソッドを呼び出す前に、組み込みロックを取得する必要があります。取得しないとブロックされます。

Bank.java を変更しました


package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private int count =0;//账户余额 

  //存钱 
  public synchronized void addMoney(int money){ 
    count +=money; 
    System.out.println(System.currentTimeMillis()+"存进:"+money); 
  } 

  //取钱 
  public synchronized void subMoney(int money){ 
    if(count-money < 0){ 
      System.out.println("余额不足"); 
      return; 
    } 
    count -=money; 
    System.out.println(+System.currentTimeMillis()+"取出:"+money); 
  } 

  //查询 
  public void lookMoney(){ 
    System.out.println("账户余额:"+count); 
  } 
}

実行結果を見てください:


余额不足 
账户余额:0 

余额不足 
账户余额:0 

1441790837380存进:100 
账户余额:100 

1441790838380取出:100 
账户余额:0 
1441790838380存进:100 
账户余额:100 

1441790839381取出:100 
账户余额:0

これはすぐに理解できるように感じます。


注: synchronized キーワードは静的メソッドを変更することもできます。このときに静的メソッドが呼び出される場合、クラス全体がロックされます

(2) 同期されたコード ブロック

つまり、同期されたコード ブロックによって変更されるステートメント ブロックです。キーワード。このキーワードによって変更されたステートメント ブロックは、同期を実現するために組み込みロックとともに自動的に追加されます。

Bank.java コードは次のとおりです。


package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private int count =0;//账户余额 

  //存钱 
  public  void addMoney(int money){ 

    synchronized (this) { 
      count +=money; 
    } 
    System.out.println(System.currentTimeMillis()+"存进:"+money); 
  } 

  //取钱 
  public  void subMoney(int money){ 

    synchronized (this) { 
      if(count-money < 0){ 
        System.out.println("余额不足"); 
        return; 
      } 
      count -=money; 
    } 
    System.out.println(+System.currentTimeMillis()+"取出:"+money); 
  } 

  //查询 
  public void lookMoney(){ 
    System.out.println("账户余额:"+count); 
  } 
}

実行結果は次のとおりです。効果は方法 1 とほぼ同じです。

注: 同期はオーバーヘッドの高い操作であるため、同期されたコンテンツは最小限にする必要があります。通常、メソッド全体を同期する必要はなく、同期されたコード ブロックを使用してキー コードを同期するだけです。

(3) 特殊なドメイン変数 (Volatile) を使用してスレッド同期を実現します


a. volatile キーワードは、ドメイン変数にアクセスするためのロックフリーのメカニズムを提供します b. volatile を使用してドメインを変更することは、仮想マシンにc. ドメインが他のスレッドで更新される可能性があるため、フィールドが使用されるたびに、レジスタ内の値を使用する代わりに再計算されます。また、d.volatile はアトミックな操作を提供せず、最終型変数を装飾するために使用することもできません。

Bank.java コードは次のとおりです:

余额不足 
账户余额:0 

1441791806699存进:100 
账户余额:100 

1441791806700取出:100 
账户余额:0 

1441791807699存进:100 
账户余额:100

どのように機能しますか?

rreee

またわかりにくいですか?どうしてこれなの?これは、volatile ではアトミックな操作が保証できないため、volatile は synchronized を置き換えることができないためです。さらに、volatile はコンパイラーによるコードの最適化を妨げるため、使用できない場合は適用しないでください。その原理は、スレッドが volatile で変更された変数にアクセスするたびに、キャッシュから変数を読み取るのではなくメモリから変数を読み取るため、各スレッドがアクセスする変数値は同じになるということです。これにより同期が確実に行われます。

(4) 再入ロックを使用してスレッド同期を実現する

同期をサポートするために、新しい java.util.concurrent パッケージが JavaSE5.0 に追加されました。 ReentrantLock クラスは、Lock インターフェイスを実装する再入可能な相互排他ロックであり、同期されたメソッドとブロックを使用する場合と同じ基本的な動作とセマンティクスを持ち、その機能を拡張します。 ReentrantLock クラスの一般的に使用されるメソッドは次のとおりです。 ReentrantLock(): ReentrantLock インスタンスの作成 lock(): ロックの取得unlock(): ロックの解放 注: ReentrantLock() には公平なロックを作成できるコンストラクターもありますが、プログラムを大幅に削減する可能性があります。 動作効率を高めるため、次のように Bank.java コードを変更することはお勧めできません:


package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private volatile int count = 0;// 账户余额 

  // 存钱 
  public void addMoney(int money) { 

    count += money; 
    System.out.println(System.currentTimeMillis() + "存进:" + money); 
  } 

  // 取钱 
  public void subMoney(int money) { 

    if (count - money < 0) { 
      System.out.println("余额不足"); 
      return; 
    } 
    count -= money; 
    System.out.println(+System.currentTimeMillis() + "取出:" + money); 
  } 

  // 查询 
  public void lookMoney() { 
    System.out.println("账户余额:" + count); 
  } 
}

動作効果はどうですか?

余额不足 
账户余额:0 

余额不足 
账户余额:100 

1441792010959存进:100 
账户余额:100 

1441792011960取出:100 
账户余额:0 

1441792011961存进:100 
账户余额:100

効果は最初の 2 つの方法と似ています。

synchronized キーワードがユーザーのニーズを満たすことができる場合は、コードを簡素化できる synchronized を使用してください。より高度な機能が必要な場合は、ReentrantLock クラスを使用してください。このとき、ロックの解放が間に合うように注意してください。そうしないと、通常は、finally コードでロックが解放されます

(5) ローカル変数を使用して実現します。スレッド同期

Bank.java コードは次のとおりです:


package threadTest; 

import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private int count = 0;// 账户余额 

  //需要声明这个锁 
  private Lock lock = new ReentrantLock(); 

  // 存钱 
  public void addMoney(int money) { 
    lock.lock();//上锁 
    try{ 
    count += money; 
    System.out.println(System.currentTimeMillis() + "存进:" + money); 

    }finally{ 
      lock.unlock();//解锁 
    } 
  } 

  // 取钱 
  public void subMoney(int money) { 
    lock.lock(); 
    try{ 

    if (count - money < 0) { 
      System.out.println("余额不足"); 
      return; 
    } 
    count -= money; 
    System.out.println(+System.currentTimeMillis() + "取出:" + money); 
    }finally{ 
      lock.unlock(); 
    } 
  } 

  // 查询 
  public void lookMoney() { 
    System.out.println("账户余额:" + count); 
  } 
}

操作の効果:

余额不足 
账户余额:0 

余额不足 
账户余额:0 

1441792891934存进:100 
账户余额:100 

1441792892935存进:100 
账户余额:200 

1441792892954取出:100 
账户余额:100

操作の効果を見た後、最初はなぜ入金のみが許可され、引き出しは許可されないのかと混乱しました。 ThreadLocal の原理を見てみましょう:

ThreadLocal を使用して変数を管理する場合、変数を使用する各スレッドは変数のコピーを取得します。コピーは相互に独立しているため、各スレッドは他のスレッドに影響を与えることなく、変数の独自のコピーを自由に変更できます。これで、各スレッドがコピーを実行していることがわかりました。これは、お金の入金と出金が同じナレッジ名を持つ 2 つのアカウントであることを意味します。したがって、上記の効果が発生します。

ThreadLocal と同期メカニズム

a.ThreadLocal と同期メカニズムは両方とも、マルチスレッドでの同じ変数のアクセス競合の問題を解決するためのものです。 b. 前者は、「空間を時間に交換する」方法を採用しています。後者は「時間を空間と交換する」方法を採用しています

以上がJava マルチスレッド同期のいくつかの方法の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。