Akka と Java メモリモデル

Akka と Java メモリモデル

Lightbend プラットフォーム、Scala、Akkaを含む、を使用しての主な利点は並行ソフトウェアを記述するプロセスが簡単になります。 この資料では、Lightbend プラットフォーム、特にAkkaは、並行アプリケーションで共有メモリに取り掛かる方法について説明します。

Java メモリモデル

Java 5 以前、Java メモリモデル (JMM) は不明瞭でした。共有メモリが複数のスレッドによってアクセスされたとき、すべての種類の奇妙な結果を得ることは可能でした。例えば、

  • スレッドは他のスレッドによって書き込まれる値を見えない: 可視性の問題

  • 命令が予想される順序で実行されていないによって、スレッドは他のスレッドの '不可能' 行動を観察できる: 命令の並べ替え問題。

Java 5 の JSR 133 の実装によって、これらの問題の多くは解決されてました。JMM は "happens-before" の関係に基づいて、一連の規則です。これらによって、いつ一つのメモリアクセスはもう一つの前に実行しなければならない、そして、いつメモリアクセスは順不同で起こることが許可されているのは制限されています。これらの規則の 2 つの例は以下のとおりです。

  • モニターロックルール: ロックの解除が同じロックのすべての後続の取得前に発生します。

  • volatile変数ルールは: volatile変数の書き込みは、同じvolatile変数のすべての後続の読み取りの前に起こります。

JMM は複雑に見えますが、仕様は使いやすさと、パフォーマンスと拡張性の高い並行データ構造を記述する能力のバランスを見つけようとします。

アクターと Java メモリモデル

Akkaのアクターの実装では、複数のスレッドが共有メモリ上のアクションを実行できる二つの方法があります。

  • メッセージは (例えば、別のアクターから) アクターに送信される場合。ほとんどの場合メッセージは不変ですが、そのメッセージが適切に初期化された不変オブジェクトでない時、"happens before" のルールがないなら、受信側が部分的に初期化されたデータ構造、さらに存在不可能の値 (long/double型の場合) を観察する可能性があります。

  • アクターはメッセージの処理中に、内部状態への変更を行い、後で別のメッセージモーメントを処理している間、その状態にアクセスする場合。アクターモデルでは、同じスレッドが同じアクターの異なるメッセージに対してを実行されるの保証はありませんことを認識するのが重要です。

アクター上で可視性と命令の並べ替え問題を防ぐため、Akka は次の二つの "happens before" ルールを保証します。

  • アクター送信ルール: アクターへのメッセージの送信は同じアクターによってそのメッセージの受信前に行われます。

  • アクター後続処理ルール: 一つのメッセージの処理が同じアクターによって次のメッセージの処理の前に行われます。

注釈

普通の言葉では、これは次のメッセージがそのアクターによって処理されたときに、アクターの内部フィールドへの変更が可視化されていることを意味します。あなたのアクターのフィールドは volatile 又は同等である必要はありません。

両方のルールは、同じアクターのインスタンスに対して適用され、別のアクターが使用されている場合は有効ではありません。

Futures と Java メモリモデル

Future の完了は、それに登録されたコールバックの呼び出しが実行される「前に発生します」。

我々は非finalフィールド (Javaでのfinal、Scalaでのval) の上に閉じないようにお勧めします、あなたは どうしても 非finalフィールド上を閉じることを選択したい場合、現在の値がコールバックに見えるために volatile を付ける必要があります。

参照を閉じる場合、参照先のインスタンスがスレッドセーフであることを確認する必要があります。我々はロックを使用するオブジェクトを使わないことを勧めします、パフォーマンスの問題が生じますので、そして最悪の場合、デッドロック。そして同じ危険な同期もそうです。

アクターと共有可変状態

Akka は JVM 上で動作するので、従うべきルールがいくつか残っています。

  • 内部アクター状態の上に閉じると、他のスレッドにそれをさらします

class MyActor extends Actor {
 var state = ...
 def receive = {
    case _ =>
      //Wrongs

    // Very bad, shared mutable state,
    // will break your application in weird ways
      Future { state = NewState }
      anotherActor ? message onSuccess { r => state = r }

    // Very bad, "sender" changes for every message,
    // shared mutable state bug
      Future { expensiveCalculation(sender()) }

      //Rights

    // Completely safe, "self" is OK to close over
    // and it's an ActorRef, which is thread-safe
      Future { expensiveCalculation() } onComplete { f => self ! f.value.get }

    // Completely safe, we close over a fixed value
    // and it's an ActorRef, which is thread-safe
      val currentSender = sender()
      Future { expensiveCalculation(currentSender) }
 }
}
  • メッセージは不変である 必要 があり、これは、共有可変状態トラップを回避することです。

Contents