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

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

この記事からわかること

  • Android Studio/Kotlinスレッドセーフ(排他制御)を実装するには?
  • synchronized使い方
  • @Synchronizedアノテーションの使い方
  • Coroutinesで使うMutexの使い方

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

スレッドセーフ(排他制御)な実装

Kotlinで複数のスレッドからの同時アクセスを防ぐための手段はいくつか存在します。今回はその中でもsynchronizedブロックや@Synchronizedアノテーションの使い方をまとめていきます。

またMutexというCoroutines用の排他制御の使い方も一緒に解説しておきます。

synchronizedブロック

公式リファレンス: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")
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article