【Java】同期修飾子(synchronized)について
Javaの同期修飾子(synchronized)の詳細解説
synchronizedとは何か
Javaのsynchronized
は、マルチスレッド環境におけるスレッドセーフティを実現するためのキーワードです。主に複数のスレッドが同時に同じリソースにアクセスする際の競合状態を防ぐために使用されます。synchronized
は、排他制御を実現することでデータの一貫性を保ちます。
メソッドレベルの同期
メソッド全体にsynchronized
を付与することで、単一のスレッドのみがそのメソッドを実行できるようになります。以下は例です:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
上記の例では、increment
とgetCount
はどちらもsynchronized
が付いているため、同時に複数のスレッドがこれらのメソッドを実行することはありません。
ブロックレベルの同期
メソッド全体ではなく、特定のコードブロックに対してのみ同期を行いたい場合は、synchronized
ブロックを使用します。例を示します:
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
この方法では、必要な部分だけ同期を行うことでパフォーマンスを向上させることができます。
静的同期とクラスロック
静的メソッドにsynchronized
を付与する場合、クラス全体に対してロックがかかります。以下の例を見てみましょう:
class StaticCounter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
静的同期は、インスタンスごとのロックではなく、クラスレベルのロックを適用します。複数のインスタンスが存在しても、同期された静的メソッドは同時に実行されません。
synchronizedのベストプラクティス
- 同期は最小限の範囲で行う:パフォーマンスの低下を防ぐため、必要な部分だけを同期する。
- ロックオブジェクトを明確に定義する:
this
の使用を避け、専用のロックオブジェクトを用いる。 - デッドロックを避ける:複数のロックを使用する場合は取得順序に注意する。
よくある間違い
- ロックの競合:複数のスレッドが同時に異なるリソースをロックする際に競合が発生することがあります。
- 過剰な同期:同期範囲を広げすぎるとパフォーマンスが低下します。
- ロックの未解放:カスタムロックを使用する場合、例外発生時にロックが解放されないことがあります。
synchronizedの代替手段
Javaではsynchronized
以外にも同期を実現するための方法があります。以下にいくつか例を挙げます:
- ReentrantLock:より柔軟なロック機構を提供する
java.util.concurrent.locks.ReentrantLock
。 - Atomic変数:単純なカウンタやフラグには
java.util.concurrent.atomic
パッケージのクラスを使用する。 - ReadWriteLock:読み取り専用の操作を複数スレッドで行いたい場合に有用。