synchronized() と withLock() を使用して Kotlin で共有リソースをロックする

David Mbochi Njonge 2023年6月20日
  1. Kotlin プロジェクトを生成する
  2. 実際のスレッド干渉
  3. Kotlin で synchronized() 関数を使用して共有リソースをロックする
  4. Kotlin で withLock() 関数を使用して共有リソースをロックする
  5. まとめ
synchronized() と withLock() を使用して Kotlin で共有リソースをロックする

マルチスレッド アプリケーションを使用する場合、共有リソースへのアクセスを確実に管理して、データの不整合を回避する必要があります。 同期は、アプリケーション内の共有リソースへのアクセスを制御する場合に通常使用するアプローチです。

同期はロックを使用して実現され、現在実行中のスレッドの実行が完了するまで、スレッドが共有リソースにアクセスしないようにします。 スレッドは、共有リソースにアクセスする前にまずオブジェクトのロックを取得し、完了したら解放する必要があります。

ロックを作成するにはさまざまな方法があります。このチュートリアルでは、Kotlin でロックを作成して共有リソースへのアクセスを制御する方法を学習します。

Kotlin プロジェクトを生成する

IntelliJ 開発環境を開き、File > New > Project を選択します。 開いたウィンドウで、プロジェクト名をkotlin-withlockと入力し、言語セクションでKotlinを選択し、ビルドシステムとしてIntellijを選択します。 最後に、Create ボタンを押してプロジェクトを生成します。

アプリケーションが生成されたら、src/main/kotlin フォルダーの下に Main.kt という名前のファイルを作成します。 このファイルを使用して、このチュートリアルのすべてのコード例を実装します。

実際のスレッド干渉

スレッド干渉は、共有リソースへのアクセス方法を制御しないマルチスレッド アプリケーションで発生します。 次のコードは、マルチスレッド アプリケーションでスレッド干渉がどのように発生するかを示しています。

次のコードをコピーして Main.kt ファイルに貼り付けます。

class Calculator {
    fun addIntArray() {
        var initialValue = 0;
        for (number in 1..5) {
            println(
                Thread.currentThread().name
                        + " ---> " + initialValue + " + "
                        + number + " = "
                    .plus(String.format("%d", (initialValue + number)))
            );
            initialValue += number;

            Thread.sleep(500);
        }
    }
}

fun main() {
    val calculator = Calculator();

    Thread {
        calculator.addIntArray();
    }.start();

    Thread {
        calculator.addIntArray();
    }.start();
}

このコードでは、Calculator という名前のクラス内に addIntArray() という名前のメソッドを作成しました。 このメソッドはコード内の共有リソースです。複数のスレッドを作成するため、現在実行中のスレッドをコンソールに記録できる println() メソッドを追加しました。

sleep() メソッドが呼び出されると、現在実行中のスレッドが 500 ミリ秒間中断され、別のスレッドが実行を開始します。 これは、スレッドがメソッドに同時にアクセスすることを意味し、データの不整合が発生する可能性があります。

main() メソッドでは、2つのスレッドを作成し、それらに対して start() メソッドを呼び出して、作成した電卓オブジェクトの addIntArray() メソッドの実行を開始しました。

このコードを実行し、以下に示すようにスレッドがどのようにインターリーブされるかを確認します。

Thread-0 ---> 0 + 1 = 1
Thread-1 ---> 0 + 1 = 1
Thread-0 ---> 1 + 2 = 3
Thread-1 ---> 1 + 2 = 3
Thread-1 ---> 3 + 3 = 6
Thread-0 ---> 3 + 3 = 6
Thread-1 ---> 6 + 4 = 10
Thread-0 ---> 6 + 4 = 10
Thread-0 ---> 10 + 5 = 15
Thread-1 ---> 10 + 5 = 15

Kotlin で synchronized() 関数を使用して共有リソースをロックする

この例では、前のセクションで実装した addIntArray() メソッドを使用して、アクセスがどのように管理されるかを示します。 前の例で作成した main() メソッドをコメントアウトし、次のコードをコピーして Main.kt ファイルのコメントの後に貼り付けます。

val calculator = Calculator();

fun sumUsingSynchronization(): Unit = synchronized(calculator) {
    calculator.addIntArray();
}

fun main() {

    Thread {
       sumUsingSynchronization();
    }.start();

    Thread {
        sumUsingSynchronization()
    }.start();
}

このチュートリアルの主な目的は、共有リソースへのスレッドの排他的アクセスを確保するためにロックを作成する方法を学習することです。 デフォルトでは、オブジェクトには、スレッドが同期を達成するために使用する暗黙的なモニター ロックがあり、このコードでは Calculator オブジェクトを synchronized() 関数に渡すことで暗黙的なモニター ロックを使用しています。

synchronized() 関数は、sumUsingSynchronization() 関数によって呼び出され、次に addIntArray() メソッドを呼び出します。 main() メソッドでは、2つのスレッドを作成し、それらに対して start() メソッドを呼び出して実行を開始しました。

このコードでは暗黙的なモニター ロックを使用しているため、2つのスレッド間に干渉がないことに注意してください。 最初のスレッドは、2 番目のスレッドが開始する前に実行が完了するまで addIntArray() メソッドのみを使用します。

synchronized() 関数は、暗黙的な監視ロックに限定されるだけでなく、ReentrantLock などの他のロックも使用でき、このメソッドの引数としてロックを渡すだけで済みます。 このコードを実行して、出力が次のようになっていることを確認します。

Thread-0 ---> 0 + 1 = 1
Thread-0 ---> 1 + 2 = 3
Thread-0 ---> 3 + 3 = 6
Thread-0 ---> 6 + 4 = 10
Thread-0 ---> 10 + 5 = 15
Thread-1 ---> 0 + 1 = 1
Thread-1 ---> 1 + 2 = 3
Thread-1 ---> 3 + 3 = 6
Thread-1 ---> 6 + 4 = 10
Thread-1 ---> 10 + 5 = 15

Kotlin で withLock() 関数を使用して共有リソースをロックする

前の例で作成した main() メソッドをコメントアウトし、次のコードをコピーしてコメントの後に Main.kt ファイルに貼り付けます。

import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

val reentrantLock: Lock = ReentrantLock();

fun sumUsingLock(): Unit = reentrantLock.withLock {
    calculator.addIntArray();
}

fun main() {

    Thread {
       sumUsingLock();
    }.start();

    Thread {
        sumUsingLock()
    }.start();
}

このコードでは、Lock インターフェイスの withLock() 拡張関数に割り当てられる sumUsingLock() という名前のメソッドを作成しました。 withLock() 拡張関数は、提供されたロック実装を使用して、作成された複数のスレッドのそれぞれに対して排他的にコード ブロックを実行します。

この例では、Lock インターフェイスを実装する ReentrantLock を使用してカスタム ロックを作成しました。 このロックは、addIntArray() メソッドを排他的に実行するために作成されたスレッドによって使用されます。

このコードを実行して、出力が次のようになっていることを確認します。

Thread-0 ---> 0 + 1 = 1
Thread-0 ---> 1 + 2 = 3
Thread-0 ---> 3 + 3 = 6
Thread-0 ---> 6 + 4 = 10
Thread-0 ---> 10 + 5 = 15
Thread-1 ---> 0 + 1 = 1
Thread-1 ---> 1 + 2 = 3
Thread-1 ---> 3 + 3 = 6
Thread-1 ---> 6 + 4 = 10
Thread-1 ---> 10 + 5 = 15

まとめ

このチュートリアルでは、これらのリソースでロックが作成される方法をカバーすることにより、共有リソースへのアクセスを管理する方法を学びました。 干渉がどのように発生するか、synchronized() 関数でロックを使用する方法、および withLock() 関数でロックを使用する方法について説明しました。

これら 2つのアプローチの違いは、synchronized() 関数はロックの実装に制限されていませんが、withLock() 関数は Lock の実装に制限されていることに注意してください。

David Mbochi Njonge avatar David Mbochi Njonge avatar

David is a back end developer with a major in computer science. He loves to solve problems using technology, learning new things, and making new friends. David is currently a technical writer who enjoys making hard concepts easier for other developers to understand and his work has been published on multiple sites.

LinkedIn GitHub