同時実行性、ブロッキングキュー (Java 7) - 1

「こんにちは、アミーゴ!」

「やあ、キム!」

「今日は同時実行性についてお話します。」

"同時実行性は、複数のスレッドからの作業用に最適化された特別なクラスを含む Java クラス ライブラリです。これは非常に興味深く広範なトピックです。しかし、今日は概要だけを説明します。パッケージは java.util と呼ばれます。 concurrent パッケージ。いくつかの興味深いクラスについてお話します。」

原子タイプ

「count++ でさえスレッドセーフな操作ではないことはすでにご存知でしょう。変数が 1 ずつインクリメントされると、実際には 3 つの操作が行われます。その結果、変数が変更されたときに競合が発生する可能性があります。」

「そう、エリーが少し前にこう言ったんです。」

スレッド 1 スレッド 2 結果
register1 = count;
register1++;
count = register1;
register2 = count;
register2++;
count = register2;
register1 = count;
register2 = count;
register2++;
count = register2;
register1++;
count = register1;

「その通りです。その後、Java はこれらの操作を 1 つとして、つまりアトミックに (アトムは分割不可能です) 実行するデータ型を追加しました。」

「たとえば、Java にはAtomicInteger、AtomicBoolean、AtomicDoubleなどがあります。」

「«counter» クラスを作成する必要があるとします。」

class Counter
{
 private int c = 0;

 public void increment()
 {
  c++;
 }

 public void decrement()
 {
  c--;
 }

 public int value()
 {
  return c;
 }
}

「このクラスのオブジェクトをスレッドセーフにするにはどうすればよいでしょうか?」

「そうですね、すべてのメソッドを同期させて完了します。」

class synchronized Counter
{
 private int c = 0;

 public synchronized void increment()
 {
  c++;
 }

 public synchronized void decrement()
 {
  c--;
 }

 public synchronized int value()
 {
  return c;
 }
}

「よくできました。しかし、アトミック タイプを使用するとどうなるでしょうか。」

class AtomicCounter
{
 private AtomicInteger c = new AtomicInteger(0);

 public void increment()
 {
  c.incrementAndGet();
 }

 public void decrement()
 {
  c.decrementAndGet();
 }

 public int value()
 {
  return c.get();
 }
}

「あなたのクラスと私のクラスはどちらも同じように動作しますが、AtomicInteger を備えたクラスの方が速く動作します。」

「まあ、ちょっとした違いかな?」

「はい。私の経験に基づいて、常に synchronized を使用することをお勧めします。すべてのアプリケーション コードが作成され、最適化プロセスが開始された場合にのみ、アトミック タイプを使用するようにコードの書き換えを開始する必要があります。しかし、いずれにせよ、私はそうしてほしかったのです。」そのような型が存在することを知る必要があります。それらを積極的に使用しない場合でも、それらが使用されているコードに遭遇する可能性は常にあります。」

「同意します。それは当然です。」

「ところで、アトミック型が不変ではないことに気づきましたか? 標準のIntegerクラスとは対照的に、 AtomicInteger には内部状態を変更するためのメソッドが含まれています。」

「分かりました。 StringStringBufferと同じです。」

「はい、そういうことですね。」

"スレッドセーフなコレクション。 "

「そのようなコレクションの例として、ConcurrentHashMap を紹介してもいいですか。HashMap をスレッドセーフにするにはどうすればよいですか?」

「すべてのメソッドを同期させますか?」

「もちろん、そのような SynchronizedHashMap が 1 つあり、それにアクセスする数十のスレッドがあると想像してください。そして、1 秒に 100 回新しいエントリがマップに追加され、その過程でオブジェクト全体が読み取りと書き込みのためにロックされます。」

「まあ、これが標準的なアプローチです。何ができるでしょうか?」

「Java の作成者は、いくつかの素晴らしいものを考え出しました。」

「まず、ConcurrentHashMap 内のデータは 1 つのブロックに保存されますが、それは「バケット」と呼ばれる部分に分割されます。そして、誰かが ConcurrentHashMap 内のデータを変更した場合、オブジェクト全体ではなく、アクセスされているバケットのみをロックします。つまり、多くのスレッドがオブジェクトを同時に変更できるのです。」

「第二に、リスト/マップの要素を反復処理し、同時にリストを変更することはできないことを覚えていますか?そのようなコードは例外をスローします。」

ループ内でコレクションの要素を反復処理し、同時に変更しないでください。
HashMap<String, Integer> map = new HashMap<String, Integer>();

for (String key: map.keySet())
{
 if (map.get(key) == 0)
  map.remove(key);
}

「しかし、ConcurrentHashMap では次のことができます。」

ループ内のコレクションの要素を反復処理し、同時に変更しないでください。」
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();

for (String key: map.keySet())
{
 if (map.get(key) == 0)
  map.remove(key);
}

「同時実行パッケージには多くの利点があります。これらのクラスを使用するには、これらのクラスをよく理解する必要があります。」

「なるほど。ありがとう、キム。本当に興味深い授業ですね。いつか名人のようにマスターしたいと思っています。」