Akka和java內存模型 22

原文:https://doc.akka.io/docs/akka/2.5/general/jmm.htmlhtml

使用Lightbend平臺(包括Scala和Akka)的一個主要好處是它簡化了編寫併發軟件的過程。本文討論Lightbend平臺,特別是Akka如何在併發應用程序中使用共享內存。java

Java內存模型

在Java 5以前,Java內存模型(JMM)定義不明確。當多個線程訪問共享內存時,有可能得到各類奇怪的結果,例如:安全

  • 一個線程沒有看到其餘線程寫的值:一個可見性問題
  • 一個線程觀察其餘線程的「impossible」行爲,這是因爲指令未按預期順序執行引發的:指令從新排序問題。

隨着Java 5中JSR 133的實現,不少這些問題都獲得瞭解決。JMM是一組基於「happens-before」關係的規則,它限制一個存儲器訪問必須在另外一個存儲器訪問以前發生。這些規則的兩個例子是:數據結構

  • 監視器鎖定規則:鎖的釋放在一系列要求獲取同一個鎖以前。
  • volatile變量規則:volatile變量的寫入在一系列查詢同一個volatile變量前。

儘管JMM看起來很複雜,但規範試圖在易用性和編寫高性能和可伸縮併發數據結構的能力之間找到平衡點。併發

Akka和java內存模型

使用Akka中的Actors實現,多個線程能夠經過兩種方式在共享內存上執行操做:app

  • 若是消息被髮送給一個Actor(例如由另外一個Actor)。在大多數狀況下,消息是不可變的,但若是該消息不是一個正確構造的不可變對象,沒有「happens before」規則,接收器就有可能看到部分初始化的數據結構,甚至多是無形的數據結構(longs/doubles)。
  • 若是一個actor在處理消息時對其內部狀態進行了更改,並在稍後處理另外一個消息時訪問該狀態。重要的是要意識到,使用actor模型,您沒法保證同一個線程將爲不一樣的消息執行相同的actor。

爲了防止Actor的可見性和從新排序問題,Akka保證如下兩個「happens before」規則:post

  • actor發送規則:消息發送給一個actor發生在同一個actor收到該消息以前。
  • actor後續處理規則:一個消息的處理在同一演員處理下一個消息以前發生。

這兩個規則僅適用於同一個actor實例,若是使用不一樣的actor,則無效。性能

若是您關閉引用,則還必須確保引用的實例是線程安全的。咱們強烈建議遠離使用鎖定的對象,由於它可能會致使性能問題,而且在最壞的狀況下會致使死鎖。這是同步的危險。this

Actor和共享的可變狀態

因爲Akka在JVM上運行,所以仍然須要遵循一些規則。spa

關閉內部Actor狀態並將其暴露給其餘線程

 import akka.actor.{ Actor, ActorRef }
  import akka.pattern.ask
  import akka.util.Timeout
  import scala.concurrent.Future
  import scala.concurrent.duration._
  import scala.language.postfixOps
  import scala.collection.mutable

  case class Message(msg: String)

  class EchoActor extends Actor {
    def receive = {
      case msg ⇒ sender() ! msg
    }
  }

  class CleanUpActor extends Actor {
    def receive = {
      case set: mutable.Set[_] ⇒ set.clear()
    }
  }

  class MyActor(echoActor: ActorRef, cleanUpActor: ActorRef) extends Actor {
    var state = ""
    val mySet = mutable.Set[String]()

    def expensiveCalculation(actorRef: ActorRef): String = {
      // this is a very costly operation
      "Meaning of life is 42"
    }

    def expensiveCalculation(): String = {
      // this is a very costly operation
      "Meaning of life is 42"
    }

    def receive = {
      case _ ⇒
        implicit val ec = context.dispatcher
        implicit val timeout = Timeout(5 seconds) // needed for `?` below

        // Example of incorrect approach
        // Very bad: shared mutable state will cause your
        // application to break in weird ways
        Future { state = "This will race" }
        ((echoActor ? Message("With this other one")).mapTo[Message])
          .foreach { received ⇒ state = received.msg }

        // Very bad: shared mutable object allows
        // the other actor to mutate your own state,
        // or worse, you might get weird race conditions
        cleanUpActor ! mySet

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

        // Example of correct approach
        // Completely safe: "self" is OK to close over
        // and it's an ActorRef, which is thread-safe
        Future { expensiveCalculation() } foreach { self ! _ }

        // Completely safe: we close over a fixed value
        // and it's an ActorRef, which is thread-safe
        val currentSender = sender()
        Future { expensiveCalculation(currentSender) }
    }
  }
  //#mutable-state
}

消息應該是不可變的,這是爲了不共享的可變狀態陷阱。

原文:https://doc.akka.io/docs/akka/2.5/general/jmm.html

相關文章
相關標籤/搜索