ホームページ  >  記事  >  Java  >  Java のスレッド セーフと非スレッド セーフの詳細な説明

Java のスレッド セーフと非スレッド セーフの詳細な説明

黄舟
黄舟オリジナル
2017-10-12 10:09:391754ブラウズ

この記事では、主に Java スレッド セーフと非スレッド セーフの分析を紹介します。これには、非スレッド セーフ現象のシミュレーションとスレッド セーフの実装が含まれます。必要な友人はそれを参照し、交換して一緒に学ぶことができます。

ArrayList と Vector の違いは何ですか?ハッシュマップとハッシュテーブルの違いは何ですか? StringBuilder と StringBuffer の違いは何ですか?これらは Java の面接でよくある基本的な質問です。このような質問に直面すると、答えは次のようになります。ArrayList は非スレッド セーフ、Vector はスレッド セーフ、HashMap は非スレッド セーフ、HashTable は非スレッド セーフ、StringBuffer はスレッド セーフです。というのは、これは昨夜暗記したばかりの「Java 面接の質問完全版」に書いてあったことだからです。この時点で、「スレッド セーフとは何ですか?」と尋ね続けるとします。スレッドセーフと非スレッドセーフの違いは何ですか?どのような状況で使用されますか?そんな疑問が次々と湧き出てきました…

非スレッドセーフ現象シミュレーション

ここではArrayListとVectorを使って説明します。

次のコードは、メイン スレッドで新しい非スレッド セーフ ArrayList を作成し、次に 1000 個のスレッドを開いてこの ArrayList に要素をそれぞれ追加します。すべてのスレッドが完了した後、この ArrayList のサイズは次のようになります。それはありますか? 10万あればいいのでは?


public class Main 
{ 
  public static void main(String[] args) 
  { 
    // 进行10次测试 
    for(int i = 0; i < 10; i++) 
    { 
      test(); 
    } 
  } 
  public static void test() 
  { 
    // 用来测试的List 
    List<Object> list = new ArrayList<Object>(); 
    // 线程数量(1000) 
    int threadCount = 1000; 
    // 用来让主线程等待threadCount个子线程执行完毕 
    CountDownLatch countDownLatch = new CountDownLatch(threadCount); 
    // 启动threadCount个子线程 
    for(int i = 0; i < threadCount; i++) 
    { 
      Thread thread = new Thread(new MyThread(list, countDownLatch)); 
      thread.start(); 
    } 
    try 
    { 
      // 主线程等待所有子线程执行完成,再向下执行 
      countDownLatch.await(); 
    } 
    catch (InterruptedException e) 
    { 
      e.printStackTrace(); 
    } 
    // List的size 
    System.out.println(list.size()); 
  } 
} 
class MyThread implements Runnable 
{ 
  private List<Object> list; 
  private CountDownLatch countDownLatch; 
  public MyThread(List<Object> list, CountDownLatch countDownLatch) 
  { 
    this.list = list; 
    this.countDownLatch = countDownLatch; 
  } 
  public void run() 
  { 
    // 每个线程向List中添加100个元素 
    for(int i = 0; i < 100; i++) 
    { 
      list.add(new Object()); 
    } 
    // 完成一个子线程 
    countDownLatch.countDown(); 
  } 
}

上記は 10 回テストされました (なぜ 10 回テストするのでしょうか? スレッド以外の安全性が毎回問題を引き起こすわけではないからです)。

出力結果:


99946
100000
100000
100000
99998
99959
100000
99975
100000
99996

上記の出力結果は、すべてのテスト結果が 100000 であるわけではないことを示しています。 ArrayList の最終サイズが 100000 未満であるテストがいくつかあり、IndexOutOfBoundsException がスローされることもあります。時々。 (この現象が発生しない場合は、何度か試してみてください)

これは、スレッドの安全性以外に起因する問題です。上記のコードを実稼働環境で使用すると、隠れた危険性やバグが存在する可能性があります。

次に、スレッドセーフ Vector を使用してテストします。上記のコードの test() メソッドで


List<Object> list = new ArrayList<Object>();



List<Object> list = new Vector<Object>();

に変更して、プログラムを実行します。


出力結果:



100000
100000
100000
100000
100000
100000
100000
100000
100000
100000

さらに数回実行したところ、問題なくすべて100000であることがわかりました。 Vector はスレッドセーフであるため、複数のスレッドが同じ Vector オブジェクト上で動作しても問題はありません。

LinkedList もスレッドセーフではないため、もう一度 LinkedList に切り替えてみてください。ArrayList でも同様の問題が発生します。


どちらを選択するか


非スレッドセーフとは、複数のスレッドが同じオブジェクトを操作するときに問題が発生する可能性があることを意味します。スレッド セーフとは、複数のスレッドが同じオブジェクトを操作するときに問題が発生しないことを意味します。


スレッドセーフティでは同期制御に多くの同期キーワードを使用する必要があるため、必然的にパフォーマンスの低下につながります。


したがって、これを使用する場合、複数のスレッドが同じオブジェクトを操作する場合は、スレッドセーフな Vector を使用し、それ以外の場合は、より効率的な ArrayList を使用してください。


非スレッドセーフ! = 安全ではありません

誰かが使用中に間違ったビューを持っています: 私のプログラムはマルチスレッドなので、ArrayList は使用できず、Vector を使用する必要があります。これは安全です。


非スレッドの安全性は、マルチスレッド環境で使用できないことを意味するものではありません。上で述べたことに注意してください。複数のスレッドが同じオブジェクト上で動作します。同じオブジェクトであることに注意してください。たとえば、最上位のシミュレーションはメイン スレッドの新しい ArrayList であり、その後、複数のスレッドが同じ ArrayList オブジェクトを操作します。


各スレッドに新しい ArrayList があり、この ArrayList がこのスレッドでのみ使用される場合は、まったく問題ありません。


スレッドセーフの実装

スレッドセーフは、synchronizedキーワードであるスレッド同期制御によって実現されます。


ここでは、コードを使用して非スレッドセーフカウンターとスレッドセーフカウンターCounterを実装し、それらに対してマルチスレッドテストを実施しました。


非スレッドセーフカウンター:



public class Main 
{ 
  public static void main(String[] args) 
  { 
    // 进行10次测试 
    for(int i = 0; i < 10; i++) 
    { 
      test(); 
    } 
  } 
  public static void test() 
  { 
    // 计数器 
    Counter counter = new Counter(); 
    // 线程数量(1000) 
    int threadCount = 1000; 
    // 用来让主线程等待threadCount个子线程执行完毕 
    CountDownLatch countDownLatch = new CountDownLatch(threadCount); 
    // 启动threadCount个子线程 
    for(int i = 0; i < threadCount; i++) 
    { 
      Thread thread = new Thread(new MyThread(counter, countDownLatch)); 
      thread.start(); 
    } 
    try 
    { 
      // 主线程等待所有子线程执行完成,再向下执行 
      countDownLatch.await(); 
    } 
    catch (InterruptedException e) 
    { 
      e.printStackTrace(); 
    } 
    // 计数器的值 
    System.out.println(counter.getCount()); 
  } 
} 
class MyThread implements Runnable 
{ 
  private Counter counter; 
  private CountDownLatch countDownLatch; 
  public MyThread(Counter counter, CountDownLatch countDownLatch) 
  { 
    this.counter = counter; 
    this.countDownLatch = countDownLatch; 
  } 
  public void run() 
  { 
    // 每个线程向Counter中进行10000次累加 
    for(int i = 0; i < 10000; i++) 
    { 
      counter.addCount(); 
    } 
    // 完成一个子线程 
    countDownLatch.countDown(); 
  } 
} 
class Counter 
{ 
  private int count = 0; 
  public int getCount() 
  { 
    return count; 
  } 
  public void addCount() 
  { 
    count++; 
  } 
}

上記のテストコードでは、1000 個のスレッドが開始され、各スレッドはカウンターを 10000 回蓄積し、最終的な出力結果は 10000000 になるはずです。


しかし、上記のコードのカウンターは同期的に制御されていないため、スレッドセーフではありません。


出力結果:



9963727
9973178
9999577
9987650
9988734
9988665
9987820
9990847
9992305
9972233

Counterをスレッドセーフなカウンターにわずかに変更します:



class Counter 
{ 
  private int count = 0; 
  public int getCount() 
  { 
    return count; 
  } 
  public synchronized void addCount() 
  { 
    count++; 
  } 
}

上記はaddCount()メソッドに同期制御を追加するだけで、Aスレッドになります。 -安全なカウンターになりました。プログラムを再度実行してください。


出力結果:



10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000

まとめ

以上がJava のスレッド セーフと非スレッド セーフの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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