Kotlinにおける Mutex(Mutual Exclusion) は、複数のコルーチンが同時に同一リソースへアクセスすることを防ぐための排他制御機構です。
特に kotlinx.coroutines を利用した 非同期・並行処理 の文脈で重要な役割を果たします。
なぜKotlinでMutexが必要なのか
Kotlinのコルーチンは非常に軽量で、多数の処理を並行実行できます。
しかし、共有状態(mutable state) をそのまま扱うと、容易にデータ競合が発生します。
var counter = 0
launch {
repeat(1000) { counter++ }
}
launch {
repeat(1000) { counter++ }
}
一見すると counter == 2000 になりそうですが、counter++ は
- 値の読み込み
- インクリメント
- 書き込み
という 複数ステップの非原子的操作であるため、並行実行時には結果が壊れる可能性があります。
このような クリティカルセクション を安全に扱うために Mutex が使われます。
KotlinのMutexの基本的な性質
コルーチン向けの排他制御
KotlinのMutexは kotlinx.coroutines.sync.Mutex として提供され、コルーチンの排他制御を目的に設計されています。
最大の特徴は次の点です。
- ロック取得待ち中に スレッドをブロックしない
- ロックが取得できるまで コルーチンがサスペンドされる
- UIスレッドや共有スレッドプールでも安全に使える
これは Java の synchronized や ReentrantLock とは本質的に異なります。
Mutexの基本的な使い方
Mutexの生成
val mutex = Mutex()
通常、保護したい共有リソースごとに1つのMutexを持ちます。
withLock(推奨)
mutex.withLock {
counter++
}
withLock は最も安全で推奨される使い方です。
- ロック取得
- 処理実行
- 例外・キャンセル発生時でも自動で
unlock
という流れを内部で保証します。
基本的にこの形式以外は使わないという認識で問題ありません。
lock / unlock(限定的用途)
mutex.lock()
try {
counter++
} finally {
mutex.unlock()
}
この形式は、
- ロックをまたいだ複雑な制御が必要な場合
- 条件付きでunlockしたい場合
など、特殊ケースのみで使われます。
unlock忘れや誤使用による IllegalStateException のリスクがあるため、通常は避けるべきです。
Mutexとキャンセルの関係
ロック待機中のキャンセル
lock() は キャンセル可能(cancellable) な suspend 関数です。
- ロック待ち中にキャンセルされた場合
→CancellationExceptionが投げられ、待機は中断されます - キャンセルとロック取得が競合した場合でも
→ 取得済みロックは正しく解放される仕様です
ロック保持中のキャンセル
withLock は内部的に try / finally 構造を持つため、
- サスペンドポイントでキャンセルが伝播すると
- finally が実行され
- Mutexは解放されます
ただし、キャンセルは サスペンドポイントで例外として顕在化するため、無限ループなどサスペンドしないコードでは即座に解放されない点は、コルーチン一般の性質として理解しておく必要があります。
ロック範囲とパフォーマンス設計
ロック内でのサスペンド処理
mutex.withLock {
delay(5000)
}
このコードは 仕様上は正しい ものの、設計としては注意が必要です。
- ロック保持中にサスペンドすると
- 他のコルーチンが長時間待たされ
- スループットやレスポンスが悪化します
そのため、
ロック範囲は最小限に保つ
という原則が非常に重要です。
「絶対に禁止」ではありませんが、ロック内で長時間待つ設計は慎重に検討すべきです。
Mutexの重要な仕様
非リエントラント(non-reentrant)
KotlinのMutexは 非リエントラント です。
- 同じコルーチンが再度
lock()を呼ぶとサスペンド ownerを指定して同一ownerで再lockすると例外
Javaの ReentrantLock とは挙動が異なるため、再帰的処理や多段ロックでは特に注意が必要です。
公平性(FIFO)
lock() は 公平(fair) に実装されており、
- 待機中のコルーチンは
- FIFO(先着順)でロックを取得します
これは予測可能性の高い挙動を保証する一方、超高スループットが求められる場面では設計判断に影響します。
メモリ可視性の保証(JVM)
JVM上では、
- unlock → 次の lock
の間に happens-before 関係が保証されます。
つまり、
- ロック内で行った変更は
- 次にロックを取得したコルーチンから確実に可視
単なる順序制御ではなく、正しいメモリセマンティクス を持つ同期機構です。
他の手段との使い分け
| ケース | 推奨 |
|---|---|
| 単純な数値・フラグ更新 | Atomic系 |
| 状態管理(UI・状態ストリーム) | StateFlow |
| イベント駆動・逐次処理 | Channel / Actor |
| 複数操作をまとめて排他 | Mutex |
Mutexは「万能」ではなく、複雑な共有状態を守るための最終手段として使うのが健全です。
まとめ
- KotlinのMutexは コルーチン向け排他制御
- ロック待ちは サスペンド(スレッドは止まらない)
- 基本は withLock一択
- ロックは 非リエントラント
- ロック範囲は 最小限
- Atomic / Flow / Channel と適切に使い分ける
以上、KotlinのMutexについてでした。
最後までお読みいただき、ありがとうございました。









