lazySet
和的set
方法有什么区别AtomicInteger
?该文档没有太多要说的lazySet
:
最终设置为给定值。
似乎存储的值不会立即设置为所需的值,而是计划在将来的某个时间设置。但是,这种方法的实际用途是什么?有什么例子吗?
lazySet
和的set
方法有什么区别AtomicInteger
?该文档没有太多要说的lazySet
:
最终设置为给定值。
似乎存储的值不会立即设置为所需的值,而是计划在将来的某个时间设置。但是,这种方法的实际用途是什么?有什么例子吗?
直接引用自“JDK-6275329:将lazySet 方法添加到原子类”:
作为 Mustang 的最后一个小 JSR166 后续行动,我们向 Atomic 类(AtomicInteger、AtomicReference 等)添加了一个“lazySet”方法。这是一种利基方法,有时在使用非阻塞数据结构微调代码时很有用。语义是保证写入不会与任何先前的写入重新排序,但可以与后续操作重新排序(或等效地,可能对其他线程不可见),直到发生其他一些易失性写入或同步操作)。
主要用例是为了避免长期垃圾保留而将非阻塞数据结构中的节点字段清空;如果其他线程暂时看到非空值,则它适用于无害的情况,但您希望确保结构最终可被 GC。在这种情况下,您可以通过避免 null volatile-write 的成本来获得更好的性能。对于非基于引用的原子,还有一些其他用例,因此所有 AtomicX 类都支持该方法。
对于喜欢将这些操作视为常见多处理器上的机器级障碍的人,lazySet 提供了一个前面的存储-存储屏障(在当前平台上是无操作或非常便宜),但没有存储-加载屏障(这通常是 volatile-write 的昂贵部分)。
lazySet 可以用于 rmw 线程间通信,因为 xchg 是原子的,至于可见性,当写入线程进程修改一个缓存行位置时,读取线程的处理器会在下一次读取时看到它,因为英特尔 cpu 的缓存一致性协议会保证LazySet 可以工作,但缓存行将在下一次读取时更新,同样,CPU 必须足够现代。
http://sc.tamu.edu/systems/eos/nehalem.pdf 对于 Nehalem 这是一个多处理器平台,处理器能够“窥探”(窃听)地址总线以供其他处理器访问系统内存和到他们的内部缓存。他们使用这种窥探能力来保持其内部缓存与系统内存和其他互连处理器中的缓存一致。如果通过窥探一个处理器检测到另一个处理器打算写入它当前已缓存为共享状态的内存位置,则窥探处理器将使其缓存块无效,迫使它在下次访问相同的内存位置时执行缓存行填充.
用于 x86 cpu 架构的 oracle 热点 jdk->
lazySet == unsafe.putOrderedLong == xchg rw(作为软屏障的 asm 指令在 nehelem intel cpu 上花费 20 个周期)
在 x86 (x86_64) 上,这样的屏障在性能方面比 volatile 或 AtomicLong getAndAdd 便宜得多,
在一个生产者,一个消费者队列的场景中,xchg 软屏障可以强制生产者线程的lazySet(sequence+1)之前的代码行发生在任何消费(处理)新数据的消费者线程代码之前,当然消费者线程需要使用 compareAndSet (sequence, sequence + 1) 原子地检查生产者序列是否恰好增加了 1。
我跟踪了 Hotspot 源代码以找到lazySet 到 cpp 代码的确切映射:http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/unsafe 。 cpp Unsafe_setOrderedLong -> SET_FIELD_VOLATILE 定义 -> OrderAccess:release_store_fence。对于 x86_64,OrderAccess:release_store_fence 被定义为使用 xchg 指令。
您可以看到它是如何在 jdk7 中准确定义的(doug lea 正在为 JDK 8 开发一些新东西): http ://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/4fc084dac61e/src/os_cpu/ linux_x86/vm/orderAccess_linux_x86.inline.hpp
您还可以使用 hdis 反汇编实际中的lazySet 代码程序集。
还有另一个相关的问题: Do we need mfence when using xchg
可以在此处找到有关lazySet 和底层 putOrdered 的起源和实用程序的更广泛讨论:http: //psy-lob-saw.blogspot.co.uk/2012/12/atomiclazyset-is-performance-win-for.html
总结一下:lazySet 是一个弱易失性写入,因为它充当存储存储而不是存储加载栅栏。这归结为lazySet被JIT编译为编译器无法重新排序的MOV指令,而不是用于易失性集的更昂贵的指令。
读取值时,您总是最终会进行 volatile 读取(在任何情况下都使用 Atomic*.get())。
lazySet 为单个 writer 提供一致的 volatile 写入机制,即单个 writer 使用 lazySet 递增计数器是完全合法的,递增相同计数器的多个线程将必须使用 CAS 解决竞争写入,这正是在incAndGet 的 Atomic* 封面。
lazySet具有写入(分配)易失性变量的记忆效应,除了它允许对后续(但不是先前)内存操作进行重新排序,这些操作本身不会对普通的非易失性写入施加重新排序约束。在其他使用上下文中,lazySet 可能在清空时应用,为了垃圾收集,一个永远不会再次访问的引用。
如果您对lazySet 感到好奇,那么您也欠自己其他解释
原子访问和更新的记忆效应通常遵循 volatile 的规则,如 Java™ 语言规范第 17.4 节所述。
get具有读取 volatile 变量的记忆效应。
set具有写入(分配)易失性变量的记忆效应。
lazySet具有写入(分配)易失性变量的记忆效应,除了它允许对后续(但不是先前)内存操作进行重新排序,这些操作本身不会对普通的非易失性写入施加重新排序约束。在其他使用上下文中,lazySet 可能在清空时应用,为了垃圾收集,一个永远不会再次访问的引用。
weakCompareAndSet 以原子方式读取和有条件地写入变量,但不会创建任何发生前的顺序,因此对于除weakCompareAndSet 的目标之外的任何变量的先前或后续读取和写入不提供任何保证。
compareAndSet和所有其他读取和更新操作(例如 getAndIncrement)具有读取和写入 volatile 变量的记忆效应。
这是我的理解,如果我错了,请纠正我:您可以将lazySet()
其视为“半”易失性:就其他线程的读取而言,它基本上是一个非易失性变量,即lazySet设置的值可能对其他人不可见线程。但是当发生另一个写操作(可能来自其他线程)时,它会变得不稳定。我能想象的 lazySet 的唯一影响是compareAndSet
. 因此,如果您使用lazySet()
,get()
从其他线程可能仍会获取旧值,但compareAndSet()
将始终具有新值,因为它是写入操作。
Re: attempt to dumb it down -
You can think of this as a way to treat a volatile field as if it wasn't volatile for a particular store (eg: ref = null;) operation.
That isn't perfectly accurate, but it should be enough that you could make a decision between "OK, I really don't care" and "Hmm, let me think about that for a bit".