記事ディレクトリ
Java マルチスレッド プログラミングでは、スレッド セーフは非常に重要な概念です。スレッド セーフとは、通常、複数のスレッドが同時に実行された場合でも、プログラムが正しい動作を維持できることを意味します。Java にはスレッド セーフを実現するための多くの方法が用意されており、この記事ではいくつかの一般的な実装アイデアを紹介します。
1. synchronized キーワードを使用する
synchronized キーワードは、Java のスレッド セーフの問題を解決する最も基本的な方法であり、コード ブロックがアトミックに実行されることを保証します。synchronized は、インスタンス メソッド、静的メソッド、コード ブロックを修飾するために使用できます。以下は、同期された変更されたインスタンス メソッドのサンプル コードです。
public class Counter {
private int count;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
上記のコードでは、increment() メソッドと getCount() メソッドの両方が synchronized によって変更されているため、一度に 1 つのスレッドのみがそれらにアクセスできます。このメソッドは単純ですが、これらのメソッドに一度にアクセスできるスレッドは 1 つだけであるため、効率は比較的低くなります。
2. ReentrantLock クラスを使用する
Java の ReentrantLock クラスは、synchronized よりも柔軟なスレッド同期メカニズムを提供します。ReentrantLock は再入可能であり、ロックを待機しているスレッドに割り込み、tryLock() メソッドを通じてロックの取得を試みることができます。以下は、ReentrantLock を使用したスレッド セーフのサンプル コードです。
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
上記のコードでは、lock.lock() を使用してロックを取得し、lock.unlock() を使用してロックを解放します。ReentrantLock を使用する場合、ロックを正しく解放するために、ロックの取得と解放のロジックを try-finally ブロックに配置する必要があることに注意してください。
3. ConcurrentHashMap クラスを使用する
ConcurrentHashMap は、Java でのスレッドセーフなハッシュ テーブル実装です。ConcurrentHashMap は、セグメント ロック メカニズムを使用してハッシュ テーブル全体を複数のセグメントに分割し、異なるセグメント内の要素に複数のスレッドから同時にアクセスできます。ConcurrentHashMap を使用したスレッド セーフのコード例を次に示します。
import java.util.concurrent.ConcurrentHashMap;
public class Counter {
private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
public void increment(String key) {
map.put(key, map.getOrDefault(key, 0) + 1);
}
public int getCount(String key) {
return map.getOrDefault(key, 0);
}
}
上記のコードでは、ConcurrentHashMap を使用してカウンターの値を保存し、map.put() および map.getOrDefault() のメソッドを使用してカウンターの値を更新および取得します。ConcurrentHashMap はスレッドセーフであるため、この実装では、複数のスレッドが同時にアクセスしたときにカウンターの値が正しいことを保証できます。
4. Atomic クラスを使用する
Java の Atomic クラスは、操作がアトミックに実行されることを保証する一連のアトミック操作を提供します。アトミック クラスには、AtomicBoolean、AtomicInteger、AtomicLong などが含まれます。AtomicInteger を使用したスレッド セーフのコード例を次に示します。
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
上記のコードでは、AtomicInteger を使用してカウンターの値を保存し、count.incrementAndGet() メソッドを使用してカウンターの値を更新します。AtomicInteger はスレッドセーフであるため、この実装では、複数のスレッドが同時にアクセスしたときにカウンターの値が正しいことを保証できます。
5. ThreadLocal クラスを使用する
ThreadLocal クラスを使用すると、各スレッドが変数の独自のコピーを持つことができ、複数のスレッドが同時に実行される場合、各スレッドは変数の独自のコピーを独立して操作できるため、スレッドの安全性の問題が回避されます。以下は、ThreadLocal を使用したスレッド セーフのサンプル コードです。
public class Counter {
private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public void increment() {
threadLocal.set(threadLocal.get() + 1);
}
public int getCount() {
return threadLocal.get();
}
}
上記のコードでは、ThreadLocal クラスを使用してカウンターの値を保存し、threadLocal.set() メソッドと threadLocal.get() メソッドを使用してカウンターの値を更新および取得します。各スレッドには変数の独自のコピーがあるため、この実装により、複数のスレッドが同時にアクセスしたときにカウンターの値が正しいことが保証されます。
要約する
この記事では、synchronized キーワード、ReentrantLock クラス、ConcurrentHashMap クラス、Atomic クラス、ThreadLocal クラスなど、Java でスレッド セーフを実現するいくつかの方法を紹介します。それぞれの手法には特徴と適用可能なシナリオがあり、実際のニーズに応じて適切な手法を選択する必要があります。実際のアプリケーションでは、システムのパフォーマンスと同時実行性を向上させるために、複数の方法を組み合わせて使用してスレッド セーフを実現できます。