概要
マルチスレッド環境で共有データを操作すると、読み書きのタイミングが重なって結果が壊れるという問題に直面します。Java の synchronized キーワードは、この排他制御を言語レベルで実現する最も基本的な仕組みです。ただし、メソッド全体にかけるのかブロック単位にするのか、ロック対象を this にするのか専用オブジェクトにするのかで、性能と安全性が変わります。この記事では、スレッドアンセーフなカウンターで競合を実際に発生させたうえで、synchronized メソッドと synchronized ブロックの2つの書き方を比較します。実務でロック粒度の判断に迷ったときの指針を示します。
使いどころ
在庫数の加減算を複数スレッドから同時に行うバッチで、カウントのズレを防止する
共有キャッシュへの読み書きを排他制御し、不整合なデータの読み出しを防ぐ
ログファイルへの書き込みを synchronized ブロックで保護し、行の混在を防止する
コード例
public class SynchronizedDemo {
// synchronized ブロック + 専用ロックオブジェクト(推奨)
static class SafeCounter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
public static void main(String[] args) throws InterruptedException {
var counter = new SafeCounter();
var threadCount = 10;
var incrementsPerThread = 1000;
var threads = new Thread[threadCount];
for (var i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
for (var j = 0; j < incrementsPerThread; j++) {
counter.increment();
}
});
threads[i].start();
}
for (var t : threads) {
t.join();
}
var expected = threadCount * incrementsPerThread;
var actual = counter.getCount();
System.out.printf("期待値=%d, 実際=%d, 一致=%b%n",
expected, actual, expected == actual);
}
}Version Coverage
パターンマッチング instanceof(Java 16+)でキャスト不要。var で型推論を活用できる。
// Java 17: パターンマッチング instanceof
if (counter instanceof UnsafeCounter c) {
c.increment();
} else if (counter instanceof SafeCounter c) {
c.increment();
}Library Comparison
注意点
synchronized メソッドは this をロック対象にするため、同じインスタンスの他の synchronized メソッドもブロックされる。粒度を細かくしたい場合は専用ロックオブジェクトを使う
count++ は読み取り・加算・書き戻しの3ステップで実行される非アトミック操作。volatile を付けただけでは競合を防げない
ロック対象に this を使うと、外部からそのインスタンスで synchronized を取られるリスクがある。private final Object lock = new Object() で専用ロックにするのが安全
synchronized 内で長時間の I/O 操作を行うとスループットが大幅に低下する。ロック範囲は最小限にとどめる
FAQ
ロック範囲を最小限にできる synchronized ブロックが基本です。メソッド全体をロックする必要がない場面で synchronized メソッドを使うと、不要なブロッキングが発生します。
ロック対象がインスタンスではなくクラスオブジェクト(Class<?>)になります。全インスタンスで共有されるため影響範囲が広く、意図しない待ちが発生しやすいです。
はい、synchronized ブロックを抜ける際にロックは自動的に解放されます。ReentrantLock と異なり、finally での明示的な unlock は不要です。