KotlinのMutexについて

採用はこちら

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++

  1. 値の読み込み
  2. インクリメント
  3. 書き込み

という 複数ステップの非原子的操作であるため、並行実行時には結果が壊れる可能性があります。

このような クリティカルセクション を安全に扱うために Mutex が使われます。

KotlinのMutexの基本的な性質

コルーチン向けの排他制御

KotlinのMutexは kotlinx.coroutines.sync.Mutex として提供され、コルーチンの排他制御を目的に設計されています。

最大の特徴は次の点です。

  • ロック取得待ち中に スレッドをブロックしない
  • ロックが取得できるまで コルーチンがサスペンドされる
  • UIスレッドや共有スレッドプールでも安全に使える

これは Java の synchronizedReentrantLock とは本質的に異なります。

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についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次