ホームページ Java &#&チュートリアル Java マルチスレッド同期のいくつかの方法の紹介

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

Sep 20, 2017 am 10:04 AM
java 方法

この記事では、主に 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 までご連絡ください。

ホットな記事タグ

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Javaの平方根 Javaの平方根 Aug 30, 2024 pm 04:26 PM

Javaの平方根

Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

Javaの完全数

Java の乱数ジェネレーター Java の乱数ジェネレーター Aug 30, 2024 pm 04:27 PM

Java の乱数ジェネレーター

Javaのアームストロング数 Javaのアームストロング数 Aug 30, 2024 pm 04:26 PM

Javaのアームストロング数

ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

ジャワのウェカ

Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

Javaのスミス番号

Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

Java Springのインタビューの質問

Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

Java 8 Stream Foreachから休憩または戻ってきますか?

See all articles