我有以下代码来设置Atomic变量(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
第二次它进行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
}
答案 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必须保持新值。因此,必须在知道是否需要该值之前对其进行计算。