Akka和java内存模型 22

爷,独闯天下 提交于 2020-03-18 18:39:55

3 月,跳不动了?>>>

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

使用Lightbend平台(包括Scala和Akka)的一个主要好处是它简化了编写并发软件的过程。本文讨论Lightbend平台,特别是Akka如何在并发应用程序中使用共享内存。

Java内存模型

在Java 5之前,Java内存模型(JMM)定义不明确。当多个线程访问共享内存时,有可能获得各种奇怪的结果,例如:

  • 一个线程没有看到其他线程写的值:一个可见性问题
  • 一个线程观察其他线程的“impossible”行为,这是由于指令未按预期顺序执行引起的:指令重新排序问题。

随着Java 5中JSR 133的实现,很多这些问题都得到了解决。JMM是一组基于“happens-before”关系的规则,它限制一个存储器访问必须在另一个存储器访问之前发生。这些规则的两个例子是:

  • 监视器锁定规则:锁的释放在一系列要求获取同一个锁之前。
  • volatile变量规则:volatile变量的写入在一系列查询同一个volatile变量前。

尽管JMM看起来很复杂,但规范试图在易用性和编写高性能和可伸缩并发数据结构的能力之间找到平衡点。

Akka和java内存模型

使用Akka中的Actors实现,多个线程可以通过两种方式在共享内存上执行操作:

  • 如果消息被发送给一个Actor(例如由另一个Actor)。在大多数情况下,消息是不可变的,但如果该消息不是一个正确构造的不可变对象,没有“happens before”规则,接收器就有可能看到部分初始化的数据结构,甚至可能是无形的数据结构(longs/doubles)。
  • 如果一个actor在处理消息时对其内部状态进行了更改,并在稍后处理另一个消息时访问该状态。重要的是要意识到,使用actor模型,您无法保证同一个线程将为不同的消息执行相同的actor。

为了防止Actor的可见性和重新排序问题,Akka保证以下两个“happens before”规则:

  • actor发送规则:消息发送给一个actor发生在同一个actor收到该消息之前。
  • actor后续处理规则:一个消息的处理在同一演员处理下一个消息之前发生。

这两个规则仅适用于同一个actor实例,如果使用不同的actor,则无效。

如果您关闭引用,则还必须确保引用的实例是线程安全的。我们强烈建议远离使用锁定的对象,因为它可能会导致性能问题,并且在最坏的情况下会导致死锁。这是同步的危险。

Actor和共享的可变状态

由于Akka在JVM上运行,因此仍然需要遵循一些规则。

关闭内部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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!