【Kotlin/Android】synchronized/@Synchronized/Mutexでスレッドセーフ(排他制御)

この記事からわかること
- Android Studio/Kotlinでスレッドセーフ(排他制御)を実装するには?
- synchronizedの使い方
- @Synchronizedアノテーションの使い方
- Coroutinesで使うMutexの使い方
index
[open]
\ アプリをリリースしました /
環境
- Android Studio:Meerkat
- Kotlin:2.0.21
- Mac M1:Sequoia 15.4
スレッドセーフ(排他制御)な実装
Kotlinで複数のスレッドからの同時アクセスを防ぐための手段はいくつか存在します。今回はその中でもsynchronized
ブロックや@Synchronized
アノテーションの使い方をまとめていきます。
またMutex
というCoroutines用の排他制御の使い方も一緒に解説しておきます。
synchronizedブロック
synchronized
ブロックは同時に1つのスレッドしか実行できないようにコードを保護することができるブロックです。引数lock
にはロック制御対象となる任意の型のオブジェクトを渡し、引数block
にスレッドセーフで行いたい処理を渡します。
inline fun <R> synchronized(lock: Any, block: () -> R): R
例えば「スレッドセーフなカウンター」を実装したい場合は以下のようになります。
var counter = 0
// ロック識別用オブジェクト
val lock = Any()
// 100個のスレッドで実行する処理を定義
val threads = List(100) {
thread {
repeat(1000) {
synchronized(lock) {
counter++
}
}
}
}
// 各スレッドの処理を実行
threads.forEach { it.join() }
// 結果を出力
println("最終カウント: $counter")
// 最終カウント: 100000
ただこれならAtomicInteger
を使用した方がわかりやすいかもしれません。
synchronized
ブロックの特徴はロック用のオブジェクトを指定できるところです。これにより異なるメソッド間でも同一のロック機構を使用してスレッドセーフな処理を定義することが可能になっています。そのため異なるロックオブジェクトを使用している場合は競合が発生する可能性があるので注意してください。
// 共通して使用するロック用オブジェクト
val lock = Any()
fun task1() {
synchronized(lock) {
println("task1 running")
Thread.sleep(1000)
}
}
fun task2() {
synchronized(lock) {
println("task2 running")
Thread.sleep(1000)
}
}
@Synchronizedアノテーション
@Synchronized
アノテーションはインスタンス単位でスレッドセーフな処理を定義するためのアノテーションです。先ほどはロック対象が任意のオブジェクトでしたが、こちらはthis
(自身)になるため、同一のインスタンスを使用している箇所に限りスレッドセーフに扱うことが可能になります。
class Counter {
private var count = 0
@Synchronized
fun increment() {
count++
}
@Synchronized
fun getCount(): Int {
return count
}
}
Mutex
先に紹介したsynchronized
/@Synchronized
はCoroutinesではうまく動作しないようでMutex
というCoroutines用の排他制御機構が用意されています。スレッドセーフにするためにはwithLock
を使用します。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun incrementSafely() {
mutex.withLock {
counter++
}
}
fun main() = runBlocking {
val scope = this
val threads = List(100) {
thread {
repeat(1000) {
scope.launch {
incrementSafely()
}
}
}
}
// 各スレッドの処理を実行
threads.forEach { it.join() }
// すべてのコルーチンが終わるのを待つ
delay(3000)
// 結果を出力
println("最終カウント: $counter")
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。