i具有以下代码来设置原子变量(java.util.concurrent.atomic
和monix.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
第二次比较时,它没有更新值,但仍调用了foo。这会导致问题,因为foo
具有副作用(在我的真实代码中,它创建了Akka 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
}
更新答案
快速答案是将此代码放入演员中,然后您不必担心同步。
如果您使用的是Akka Actors,则无需使用低级原始词进行自己的线程同步。演员模型的全部要点是将线程之间的相互作用限制为传递异步消息。这提供了您需要的所有线程同步,并确保演员一次以单线读取方式处理一条消息。
您绝对不应该拥有由创建单身演员的多个线程同时访问的函数。只需在拥有所需的信息时创建演员,然后将ActorRef
传递给其他需要使用依赖注入或消息的参与者。或在开始时创建演员并在第一个消息到达时初始化(使用context.become
来管理Actor State(。
原始答案
最简单的解决方案只是使用lazy val
保存您的foo
实例:
class Foo {
lazy val foo = {
println("called")
/* Side Effects */
"foo"
}
}
这将在第一次使用时创建foo
,然后将返回相同的值。
如果由于某种原因无法实现这一目标,请使用初始化为0
的AtomicInteger
,然后致电incrementAndGet
。如果返回1
,则是第一个通过此代码,您可以致电foo
。
说明:
诸如compareAndSet
之类的原子操作需要CPU指令集的支持,现代处理器具有单一的原子指令。在某些情况下(例如,缓存线仅由此处理器持有(,操作可能非常快。其他情况(例如,在另一个处理器的高速缓存中也在缓存线(该操作可能会较慢,并且可能会影响其他线程。
结果是,在执行原子指令之前,CPU必须持有新值。因此,必须在知道是否需要之前计算该值。