1

我有以下代码设置 Atomic 变量(两者java.util.concurrent.atomicmonix.execution.atomic行为相同:

class Foo {
  val s = AtomicAny(null: String)

  def foo() = {
    println("called")
    /* Side Effects */ 
    "foo" 
  }

  def get(): String = {
    s.compareAndSet(null, foo())
    s.get
  }
}


val f = new Foo
f.get //Foo.s set from null to foo, print called
f.get //Foo.s not updated, but still print called

第二次 compareAndSet 时,它没有更新值,但仍然调用了 foo。这导致了问题,因为foo它有副作用(在我的真实代码中,它创建了一个 Akka 演员并给我错误,因为它试图创建重复的演员)。

除非实际使用,否则如何确保不评估第二个参数?(最好不要使用同步)

我需要将隐式参数传递给 foo 所以惰性 val 不起作用。例如

  lazy val s = get() //Error cannot provide implicit parameter

  def foo()(implicit context: Context) = {
    println("called")
    /* Side Effects */ 
    "foo" 
  }

  def get()(implicit context: Context): String = {
    s.compareAndSet(null, foo())
    s.get
  }
4

1 回答 1

1

更新的答案

快速的答案是把这段代码放在一个演员里面,然后你就不用担心同步了。

如果您使用 Akka Actors,您永远不需要使用低级原语进行自己的线程同步。Actor 模型的全部意义在于将线程之间的交互限制为仅传递异步消息。这提供了您需要的所有线程同步,并保证参与者以单线程方式一次处理一条消息。

您绝对不应该有一个由多个线程同时访问的函数来创建一个单例参与者。只需在获得所需信息时创建ActorRef参与者,然后使用依赖注入或消息将其传递给需要它的任何其他参与者。或者在开始时创建actor并在第一条消息到达时对其进行初始化(context.become用于管理actor状态)。


原始答案

最简单的解决方案就是使用 alazy val来保存您的实例foo

class Foo {
  lazy val foo = {
    println("called")
   /* Side Effects */ 
   "foo" 
  }
}

这将创建foo第一次使用它,之后将返回相同的值。

如果由于某种原因无法做到这一点,请使用AtomicInteger初始化为0然后调用incrementAndGet。如果返回1,则这是第一次通过此代码,您可以调用foo.

解释:

诸如compareAndSet需要 CPU 指令集支持的原子操作,现代处理器具有用于此类操作的单个原子指令。在某些情况下(例如高速缓存线由该处理器独占),操作可能非常快。其他情况(例如,缓存行也在另一个处理器的缓存中)操作可能会明显变慢并可能影响其他线程。

结果是 CPU 必须在原子指令执行之前保存新值。因此,必须在知道是否需要之前计算该值。

于 2019-07-04T06:06:55.663 回答