即使不使用原子的compareAndSet参数,也会对其进行评估

时间:2019-07-03 23:46:38

标签: scala java.util.concurrent monix

我有以下代码来设置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 actor,并由于尝试创建重复的actor而给我一个错误)。

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

我需要将隐式参数传递给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
  }

1 个答案:

答案 0 :(得分:1)

更新的答案

快速的答案是将这段代码放在actor中,这样您就不必担心同步了。

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

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


原始答案

最简单的解决方案是使用lazy val来保存foo的实例:

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

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

如果由于某些原因无法实现,请使用初始化为AtomicInteger的{​​{1}},然后调用0。如果返回incrementAndGet,则是此代码的第一遍,您可以调用1

说明:

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

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