Synchronized: その概要、動作原理、Javaでの使用方法

著者: IT Sectr 公開日: 2026-03-19 読了時間: 8 分

SynchronizedはJava言語に組み込まれた同期メカニズムであり、コードのクリティカルセクションへの排他的アクセスを提供します。Oracle, 2024によると、synchronized修飾子は、マークされたメソッドまたはブロックを特定の時点で1つのスレッドのみが実行できることを保証します。このメカニズムはモニターに基づいています。モニターはオペレーティングシステムの基本概念であり、あらゆる複雑度のマルチスレッドアプリケーションの正確な動作を保証します。

主要ポイント

  • Synchronized — スレッドセーフなデータアクセスのためのJavaキーワード。
  • オブジェクトモニター — 同期の基盤となる内部メカニズム。
  • Synchronizedメソッド インスタンスまたはクラスレベルでメソッド全体をロック。
  • Synchronizedブロック コードの一部のみを同期可能。
  • Deadlock — ネストされた同期における主要な問題の一つ。

Synchronizedとは?

SynchronizedはJavaのキーワードで、一度に1つのスレッドのみが保護されたコードセクションを実行することを保証し、並行アクセス時のデータ破損を防ぎます。Javaの最初のバージョンで登場し、あらゆるレベルの開発者にとってスレッドセーフティを確保する最も簡単な方法であり続けています。

Javaにおける定義と役割

synchronized修飾子は2つのタスクを解決します:相互排他と変更の可視性。スレッドがsynchronizedブロックを終了すると、すべての変更は同じオブジェクトで同期されたブロックに入る他のスレッドに確実に可視になります。

Synchronizedはメソッド全体、またはモニターオブジェクトを指定して任意のコードブロックに適用できます。どちらの場合も、JVMはバイトコードレベルでmonitorenterおよびmonitorexit命令を挿入します。

登場の背景

同期なしのマルチスレッドアプリケーションでは、2つのスレッドが同時に同じデータを変更するとレースコンディションが発生し、予測不可能な結果をもたらします。Synchronizedはこの問題に対処するJava初の主要ツールとなり、どの開発者でも利用可能なシンプルな宣言的構文を提供します。

Synchronizedの仕組み

Synchronizedメカニズムは、モニターの概念に基づいています。モニターはすべてのJavaオブジェクトに組み込まれた高レベルの同期プリミティブです。モニターは、synchronizedブロックがオブジェクト上で最初に使用された時点で関連付けられます。

オブジェクトモニター

Javaのすべてのオブジェクトには関連付けられたモニターがあります。スレッドがsynchronizedブロックに入ると、オブジェクトのモニターを取得します。モニターが既に別のスレッドによって保持されている場合、スレッドは解放されるまでブロックされます。バイトコードでは、これはmonitorenterとmonitorexitの命令ペアに対応します。

ロック状態(biased locking)

JVMは複数のレベルを通じてsynchronizedを最適化します:単一スレッドアクセス用のbiased locking(偏向ロック)、低競合用のlightweight locking(軽量ロック)、OSが関与する高競合用のheavyweight locking(重量ロック)。これらのレベルはコードを変更せずにパフォーマンスを向上させます。

java
class Counter {
    private int count = 0;

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

    public synchronized int getCount() {
        return count;
    }
}

Happens-beforeルール

Synchronizedはhappens-before関係を確立します:synchronizedブロックを終了する前のスレッド内のすべてのアクションは、同じオブジェクトで同期されたブロックに入った後の別のスレッドに可視になります。これにより、相互排他だけでなく、すべてのスレッドのデータ整合性も保証されます。

Synchronizedメソッド vs ブロック

Javaはsynchronizedを適用する2つの方法を提供します:メソッドレベルとブロックレベルです。どちらを選択するかは、パフォーマンスと同期の粒度に影響します。

Synchronizedメソッド

メソッドにsynchronized修飾子を付けると、自動的に現在のインスタンス(インスタンスメソッドの場合)またはClassオブジェクト(静的メソッドの場合)で同期されます。これは相互排他を確保する最も簡単な方法ですが、クリティカルセクションがメソッドのごく一部のみを構成し、残りのコードに同期が必要ない場合には過剰になることがよくあります。

Synchronizedブロック

Synchronizedブロックは正確な制御を提供します:モニターオブジェクトを指定し、コードの必要な部分のみを同期し、メソッドの残りをロックの外に置きます。これによりモニター保持時間が最小化され、マルチスレッド環境でのアプリケーション全体のパフォーマンスが向上します。他のスレッドはモニターの解放を待たずに無関係なコードを並行して実行できます。

java
class DataProcessor {
    private final Object lock = new Object();

    public void process() {
        // クリティカルセクション外のコード - 同期なし
        prepareData()

        synchronized (lock) {
            // このブロックのみ保護されています
            updateSharedState()
        }

        // ロックなしで続行
        cleanup()
    }
}
基準SynchronizedメソッドSynchronizedブロック
モニターthis(インスタンス)またはClass任意のオブジェクト
粒度メソッド全体必要なコードのみ
可読性高い中程度
パフォーマンス大きなメソッドでは低い小さなクリティカルセクションでは高い

AndroidでのSynchronized

Android開発では、synchronizedはSharedPreferences、データベースアクセス、UIコンポーネントの保護に広く使用されています。ただし、メインスレッドでの使用はインターフェースのフリーズリスクのため強く推奨されません。

SharedPreferencesでの使用

AndroidのSharedPreferencesは基本的なスレッドセーフティを提供しますが、Editorを介して複数のスレッドから編集する場合、外部同期が必要になることがあります。別のロックオブジェクトを使用したsynchronizedブロックが変更の整合性を保証します。

kotlin
class PreferencesManager(private val prefs: SharedPreferences) {
    private val lock = Any()

    fun writeToken(token: String) {
        synchronized (lock) {
            prefs.edit()
                .putString("auth_token", token)
                .apply()
        }
    }
}

Androidアプリケーションの制限

Androidでのsynchronizedの主な制限はスレッドブロッキングです。Mutexを使用するコルーチンとは異なり、synchronizedはシステムスレッド全体をブロックします。メインスレッドでは、これがANRを引き起こします。最新のAndroid開発では、synchronizedをコルーチン(suspend Mutex)またはアトミック型(AtomicInteger)に置き換えることが推奨されています。

Synchronizedの代替手段

最新のJavaとKotlinはsynchronizedの代替手段をいくつか提供しており、それぞれが同じ問題をより少ない制限またはより良いパフォーマンスで解決します。

java.util.concurrentのLock

Lockインターフェースは、ReentrantLockおよびReadWriteLockの実装により、タイムアウト、割り込み可能な待機、複数のConditionキューを提供します。synchronizedよりも柔軟ですが、finallyでの明示的な解放が必要であり、unlockを忘れるとエラーのリスクが高まります。

アトミッククラス

AtomicInteger、AtomicLong、AtomicReferenceなどのクラスは、CAS(Compare-And-Swap)に基づくLock-Freeアルゴリズムを使用します。これらは中程度の競合シナリオでsynchronizedよりも大幅に高速です。スレッドをブロックせず、OSカーネルのコンテキストスイッチを必要とせずに楽観的再試行を実行するためです。

ThreadLocalとスレッドセーフティ

ThreadLocalは代替アプローチを提供します:各ThreadLocal変数は単一のスレッド内で分離されており、読み取りと書き込みに同期を必要としません。これにより、スレッド間で共有すべきでないデータに対するsynchronizedの必要性が完全になくなります。ThreadLocalはフレームワーク(Spring、Hibernate)でトランザクションコンテキストとセッションの保存に積極的に使用されています。

コルーチンとcomposeアプローチ

Android向けKotlinプロジェクトでは、synchronizedの代替としてkotlinx.coroutinesのMutexがあります。これはオペレーティングシステムのスレッドをブロックせず、ロックが解放されるまでコルーチンを一時停止します。これによりプールスレッドの効率的な使用が可能になり、リソース解放の長時間待機中にANRを回避できます。

kotlin
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

val mutex = Mutex()
var counter = 0

suspend fun safeIncrement() {
    mutex.withLock {
        counter++
    }
}

Synchronizedのパフォーマンス

Synchronizedのパフォーマンスは最近のJavaバージョンで大幅に変化しました。以前は“重い”メカニズムと考えられていましたが、最新のJVMは高度なJITコンパイラ最適化によりほとんどのオーバーヘッドを排除しました。実行時に仮想マシンがどのように同期コードを高速化するかを詳しく見てみましょう。

Biased LockingとLock Coarsening

JVMのJITコンパイラはいくつかの最適化を適用します:biased lockingはロックが常に同じスレッドによって取得される場合に同期を排除します;lock coarseningは隣接するsynchronizedブロックを1つに統合します;lock eliminationはオブジェクトが1つのスレッドからのみアクセス可能な場合に同期を削除します。これらの最適化により、低競合時にはsynchronizedは実質的にコストフリーになります。

競合の測定と最適化の選択

JVMは各オブジェクトの競合レベルを決定します:競合がない場合はbiased lockingが有効になり、2番目のスレッドが現れるとロックはスピン待機の lightweightモードに移行し、長時間の待機時にのみシステムミューテックスを使用するheavyweightにエスカレーションします。このエスカレーションは自動的に発生し、開発者が手動で戦略を選択する必要はありません。

Lockおよびアトミッククラスとの比較

最新のベンチマーク(Java 17+)では、synchronizedは低および中程度の競合でReentrantLockと同等のパフォーマンスを示します。高競合時には、タイムアウトと割り込みをサポートするより効率的な待機キューによりLockが優位になる場合があります。競合が一定である高負荷システムでは、fairモードのReentrantLockがより予測可能な動作を提供します。

アトミッククラス(AtomicInteger、AtomicReference)は、CASベースのLock-Free実装により、単純なカウンターとフラグに対して最速のままです。これらはスレッドをまったくブロックしません。競合が発生した場合、操作はループ内で単純に再試行されます。これにより、4〜8スレッドでのカウンターインクリメント操作において、synchronizedと比較して3〜5倍のパフォーマンス向上が得られます。

よくある質問

synchronizedとvolatileの違いは何ですか?

Synchronizedは相互排他と可視性の両方を提供します。Volatileは変更の可視性のみを保証します。volatile変数への書き込みはすべてのスレッドに可視ですが、同時変更を防ぐことはできません。つまり、レースコンディションから保護しません。

synchronizedはデッドロックを引き起こす可能性がありますか?

はい、異なるモニター順序でのネストされた同期によりデッドロックが発生する可能性があります。例えば、あるスレッドがsynchronized(a) { synchronized(b) }を呼び出し、別のスレッドがsynchronized(b) { synchronized(a) }を呼び出します。ネストされたsynchronizedブロックを避けるか、一貫したモニター順序を固定してください。

Javaにおけるモニターとは何ですか?

モニターは各Javaオブジェクトに関連付けられた同期メカニズムです。そのオブジェクト上で1つのスレッドのみがsynchronizedコードを実行することを保証します。モニターにはロック、待機キュー、およびwait/notifyによる通知を待つスレッドのプールが含まれます。

Lockはsynchronizedより速いですか?

最新のJavaバージョン(17+)では、synchronizedはJIT最適化(biased locking、lock coarsening)のおかげでパフォーマンスにおいてLockに劣りません。Lockが好まれるのは速度ではなく、タイムアウト、割り込み可能な待機、複数のConditionキューといった追加機能のためです。

静的メソッドでsynchronizedはどのように機能しますか?

静的synchronizedメソッドは、インスタンスではなく指定されたクラスのClassオブジェクトのモニターを使用します。これは、同期がクラスのすべてのインスタンスに適用されることを意味します。非静的メソッドと静的synchronizedメソッドは異なるモニターを使用し、互いにブロックしません。

まとめ

  • Synchronized — モニターに基づくJavaの組み込み同期メカニズム。
  • オブジェクトモニター — 相互排他を保証するJVM内部構造。
  • wait/notifyメソッドはsynchronizedブロックまたはメソッド内でのみ使用。
  • Synchronizedブロックはより細かい同期粒度のためメソッドより推奨。
  • Happens-beforeは同じオブジェクトでの同期時、スレッド間の変更の可視性を保証。
  • Deadlock — 異なるモニター順序でのネストされた同期の主なリスク。
  • 代替手段 — Lock、アトミッククラス、suspending Mutexを使用したコルーチン。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください