Answers:
直接引自“ JDK-6275329:向原子类添加lazySet方法”:
作为Mustang的最后一个JSR166后续文章,我们向Atomic类(AtomicInteger,AtomicReference等)添加了“ lazySet”方法。这是一种利基方法,在使用非阻塞数据结构微调代码时,有时会很有用。语义是保证写操作不会与任何先前的写操作重新排序,而是可以与后续操作重新排序(或等效地,可能对其他线程不可见),直到发生其他一些易失性写操作或同步操作为止。
主要用例是仅出于避免长期垃圾保留的目的,使非阻塞数据结构中的节点字段无效。如果其他线程在一段时间内看到非null值是无害的,则可以使用它,但是您要确保结构最终可用于GC。在这种情况下,可以避免空volatile写入的开销,从而获得更好的性能。这些路线上还有其他一些基于非引用的原子的用例,因此所有AtomicX类都支持该方法。
对于喜欢从通用多处理器上的机器级障碍来考虑这些操作的人们,lazySet提供了先行的存储障碍(在当前平台上是无操作或非常便宜的),但没有存储负载障碍(通常是易失性写入的昂贵部分)。
Atomic*
in作用域的所有线程都是可见的)。
lazySet可用于rmw线程间通信,因为xchg是原子的,至于可见性,当写入器线程进程修改高速缓存行位置时,读取器线程的处理器将在下次读取时看到它,因为intel 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(序列,序列+ 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中精确定义的(dou 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代码的程序集。
还有另一个相关的问题: 使用xchg时是否需要mfence
可以在以下位置找到对lazySet的起源和实用程序以及底层putOrdered的更广泛讨论:http ://psy-lob-saw.blogspot.co.uk/2012/12/atomiclazyset-is-performance-win-for.html
总结:lazySet是弱易失性写入,从某种意义上说,它充当存储商店而不是存储负载的篱笆。这归结为lazySet被JIT编译为MOV指令,该指令不能由编译器重新排序,而是要对易失性集使用明显昂贵的指令。
读取值时,您总是最终进行易失性读取(无论如何都要使用Atomic * .get())。
lazySet为单个编写器提供了一致的易失性写机制,即,单个编写器使用lazySet递增计数器是完全合法的,递增同一计数器的多个线程将必须使用CAS解决竞争写操作,这正是在以下情况下发生的情况用于incAndGet的Atomic *的封面。
StoreStore
障碍,但不是一个StoreLoad
?
lazySet具有写入(分配)易失性变量的内存效果,除了它允许对后续(但不是先前)的内存操作进行重新排序,而这些内存操作本身不会对普通的非易失性写入施加强加约束。在其他使用上下文中,为了进行垃圾回收,lazySet可能在清空时适用,以后再也不会访问该引用。
如果您对lazySet感到好奇,那么您也应该欠自己其他解释
如Java™语言规范的第17.4节所述,用于访问和更新原子的内存效应通常遵循关于易失性的规则。
get具有读取易失性变量的存储效果。
set具有写入(分配)volatile变量的存储效果。
lazySet具有写入(分配)易失性变量的内存效果,除了它允许对后续(但不是先前)的内存操作进行重新排序,而这些内存操作本身不会对普通的非易失性写入施加强加约束。在其他使用上下文中,为了进行垃圾回收,lazySet可能在清空时适用,以后再也不会访问该引用。
weakCompareAndSet原子地读取和有条件地写入变量,但不会在排序之前创建任何事件,因此,对于除weakCompareAndSet的目标以外的任何变量的先前或后续读取和写入,不提供任何保证。
compareAndSet和所有其他读取和更新操作(例如getAndIncrement)具有读取和写入易失性变量的内存影响。
这是我的理解,如果我做错了,请纠正我:您可以将其lazySet()
视为“半”易失性:就其他线程读取而言,它基本上是非易失性变量,即lazySet设置的值可能对其他线程不可见线程。但是,当发生另一个写操作(可能来自其他线程)时,它会变得不稳定。我能想象到的lazySet的唯一影响是compareAndSet
。因此,如果您使用lazySet()
,则get()
其他线程可能仍会获得旧值,但compareAndSet()
由于它是写操作,因此将始终具有新值。
compareAndSet
吗