概要
Kotlin の Mutex(ミューテックス)は、
複数のコルーチンが同じリソースを同時に扱わないようにするための排他ロック機構です。
スレッドレベルのロック(synchronized)とは異なり、
軽量・非ブロッキング・サスペンド対応 が特徴です。
一言で言うと
「コルーチンの世界における synchronized」
1. 基本構文:Mutex と withLock
Kotlin Coroutines の Mutex は kotlinx.coroutines.sync パッケージに含まれています。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
val mutex = Mutex()
var counter = 0
val jobs = List(1000) {
launch {
mutex.withLock {
counter++
}
}
}
jobs.joinAll()
println("Counter = $counter")
}
ポイント:
-
Mutex()はスレッドではなくコルーチン単位での排他 -
withLock { ... }ブロック内は同時に1コルーチンしか実行されない -
lock()/unlock()でも操作可能(低レベルAPI)
2. lock() / unlock() の手動制御
withLock {} は便利な構文ですが、
明示的に制御したい場合は lock() / unlock() を使えます。
val mutex = Mutex()
var counter = 0
val job = launch {
mutex.lock()
try {
counter++
} finally {
mutex.unlock()
}
}
withLock の糖衣構文
mutex.withLock {
counter++
}
これは上記コードと同義です。
3. Mutex と synchronized の違い
| 比較項目 | synchronized |
Mutex |
|---|---|---|
| スコープ | スレッド | コルーチン |
| 実行ブロック | ブロッキング | サスペンド |
| 実装レイヤ | JVM(OSレベル) | kotlinx.coroutines |
| デッドロック回避 | 難しい | 比較的安全(構造化並行性) |
| 可搬性 | Java限定 | KMP(マルチプラットフォーム)対応 |
つまり:
-
Mutexは軽量で非ブロッキング - スレッドを止める代わりに、コルーチンを中断(suspend) させる
4. 典型的な適用場面
カウンタ・共有リストの更新
val mutex = Mutex()
val sharedList = mutableListOf<Int>()
coroutineScope {
repeat(5) { i ->
launch {
mutex.withLock {
sharedList.add(i)
}
}
}
}
println(sharedList)
ファイル書き込み・ログ出力
val fileMutex = Mutex()
suspend fun safeLog(line: String) {
fileMutex.withLock {
println("[${Thread.currentThread().name}] $line")
// 実際は FileWriter.append(line)
}
}
5. Mutex vs Channel
| 概念 | 用途 | 挙動 |
|---|---|---|
| Mutex | 共有リソースのロック | 「同時アクセスを防ぐ」 |
| Channel | データの送受信 | 「データを順番に流す」 |
選び方:
- 「共有状態(変数やリスト)を守りたい」→
Mutex - 「値を順番に渡したい」→
Channel
6. tryLock() による非ブロッキングロック
ブロック(サスペンド)せずにロックを試みたい場合:
if (mutex.tryLock()) {
try {
println("Locked successfully!")
} finally {
mutex.unlock()
}
} else {
println("Already locked, skipping...")
}
用途:
- タイムアウト付きリソースアクセス
- 同時実行をスキップしたいケース
7. Mutex と withTimeout の組み合わせ
時間制限付きロックも可能です。
import kotlinx.coroutines.withTimeout
val mutex = Mutex()
suspend fun timedWork() {
withTimeout(500L) {
mutex.withLock {
println("Acquired lock within timeout!")
delay(1000L)
}
}
}
500ms 以内にロックを取得できなければ TimeoutCancellationException が発生します。
これにより「デッドロック回避」や「リソース待機の制限」が可能。
8. 実戦例:構造化並行性 × Mutex
複数のワーカーが共有カウンタを安全に扱うケース:
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
suspend fun safeCounterExample() = coroutineScope {
val mutex = Mutex()
var counter = 0
val workers = List(5) {
launch {
repeat(100) {
delay(10)
mutex.withLock { counter++ }
}
}
}
workers.joinAll()
println("Final counter = $counter")
}
fun main() = runBlocking {
safeCounterExample()
}
結果:
Mutex がなければレースコンディションで値が欠損するが、
withLock により 確実に100×5=500 が出力される。
9. FlowやActorとの連携
Mutexは 状態保護(State Protection) に使い、
FlowやActorは**データフロー制御**に使います。
例:StateFlow 更新を排他制御する
val mutex = Mutex()
val _state = MutableStateFlow(0)
suspend fun incrementSafely() {
mutex.withLock {
_state.value++
}
}
StateFlow は複数コルーチンから書き換え可能なので、
安全な更新には Mutex が最適。
まとめ
| 概念 | 説明 |
|---|---|
Mutex |
コルーチン用の非ブロッキングロック |
withLock {} |
ロックを自動開放する安全ブロック |
tryLock() |
即時取得を試みる |
withTimeout |
時間制限付きロック |
lock() / unlock()
|
低レベル制御 |
Channelとの違い |
Channelはデータ伝達、Mutexは状態保護 |
| 使用目的 | 共有リソースの排他、データ整合性維持 |