AtomicXXX.lazySet(...) 发生在边缘之前
大多数 JMM 推理中使用的 AtomicXXX.lazySet(value) 方法在边发生之前意味着什么? javadocs 是纯粹的,Sun bug 6275329 指出:
语义是保证写入不会与任何先前的写入一起重新排序,但可能会与后续操作一起重新排序(或者等效地,可能对其他线程不可见),直到发生其他一些易失性写入或同步操作)。
但这不是关于 HB 边缘的推理,所以它让我感到困惑。这是否意味着lazySet()语义不能用HB边来表达?
更新:我会尝试具体化我的问题。我可以在以下场景中使用普通的易失性字段:
//thread 1: producer
...fill some data structure
myVolatileFlag = 1;
//thread 2: consumer
while(myVolatileFlag!=1){
//spin-wait
}
...use data structure...
在这种情况下,在消费者中使用“数据结构”是正确的,因为易失性标志读写使HB边缘,保证生产者对“数据结构”的所有写入都将完成,并且消费者可见。但是,如果我在这种情况下使用 AtomicInteger.lazySet/get 而不是 易失性写入/读取呢?
//thread 1: producer
...fill some data structure
myAtomicFlag.lazySet(1);
//thread 2: consumer
while(myAtomicFlag.get()!=1){
//spin-wait
}
...use data structure...
它仍然是正确的吗?我仍然可以真正了解“数据结构”值在消费者线程中的可见性吗?
这不是“空中”问题——我在这种情况下的 LMAX Disruptor 代码中看到过这种方法,但我不明白如何证明它是正确的......
What does mean AtomicXXX.lazySet(value) method in terms of happens-before edges, used in most of JMM reasoning? The javadocs is pure on it, and Sun bug 6275329 states:
The semantics are that the write is guaranteed not to be re-ordered with any previous write, but may be reordered with subsequent operations (or equivalently, might not be visible to other threads) until some other volatile write or synchronizing action occurs).
But this not a reasoning about HB edges, so it confuses me. Does it mean what lazySet() semantics can't be expressed in terms of HB edges?
UPDATE: I'll try to concretize my question. I can use ordinary volatile field in following scenario:
//thread 1: producer
...fill some data structure
myVolatileFlag = 1;
//thread 2: consumer
while(myVolatileFlag!=1){
//spin-wait
}
...use data structure...
In this scenario use of "data structure" in consumer is correct, since volatile flag write-read make HB edge, giving guarantee what all writes to "data structure" by producer will be completed, and visible by consumer. But what if I'll use AtomicInteger.lazySet/get instead of volatile write/read in this scenario?
//thread 1: producer
...fill some data structure
myAtomicFlag.lazySet(1);
//thread 2: consumer
while(myAtomicFlag.get()!=1){
//spin-wait
}
...use data structure...
will it be still correct? Can I still really on "data structure" values visibility in consumer thread?
It is not "from air" question -- I've seen such method in LMAX Disruptor code in exactly this scenario, and I don't understand how to prove it is correct...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
lazySet
操作不会创建happens-before 边,因此不能保证立即可见。这是一种低级优化,只有少数用例,主要是并发数据结构。清空链表指针的垃圾收集示例没有用户可见的副作用。首选清零,以便如果列表中的节点处于不同代,则不会强制执行更昂贵的收集来丢弃链接链。使用lazySet可以保持卫生语义,而不会产生不稳定的写入开销。
另一个例子是使用由锁保护的易失性字段,例如在 ConcurrentHashMap 中。这些字段是易失性的,以允许无锁读取,但写入必须在锁定下执行,以确保严格的一致性。由于锁保证了释放时的发生前边缘,因此优化是在写入字段时使用lazySet,并在解锁时刷新所有更新。这有助于避免不必要的停顿和总线交通,从而缩短关键部分。
如果您编写并发数据结构,那么lazySet 是一个需要注意的好技巧。它是低级优化,因此仅在性能调整时才值得考虑。
The
lazySet
operations do not create happens-before edges and are therefore not guaranteed to be immediately visible. This is a low-level optimization that has only a few use-cases, which are mostly in concurrent data structures.The garbage collection example of nulling out linked list pointers has no user-visible side effects. The nulling is preferred so that if nodes in the list are in different generations, it doesn't force a more expensive collection to be performed to discard the link chain. The use of lazySet maintains hygenic semantics without incurring volatile write overhead.
Another example is the usage of volatile fields guarded by a lock, such as in
ConcurrentHashMap
. The fields are volatile to allow lock-free reads, but writes must be performed under a lock to ensure strict consistency. As the lock guarantees the happens-before edge on release, an optimization is to use lazySet when writing to the fields and flushing all of the updates when unlocking. This helps keep the critical section short by avoiding unnecessary stalls and bus traffic.If you write a concurrent data structure then
lazySet
is a good trick to be aware of. Its a low-level optimization so its only worth considering when performance tuning.基于 Unsafe 的 Javadoc(AtomicInteger.lazySet 中使用 putOrderedInt)
AtomicXXX 类中的支持字段是易失性的。 lazySet 似乎会写入这些字段,就好像它们不是易失性的一样,这会删除您期望的发生在边缘之前。正如您的链接中所述,这对于使值符合 GC 资格而无需引起易失性写入非常有用。
编辑:
这是为了回答您的更新。
如果您查看链接中提供的报价,那么您将失去易失性写入的任何内存保证。
这个lazySet不会被排序在它被写入的地方之上,但是如果没有任何其他实际的同步,你就失去了消费者将看到在它之前写入的任何更改的保证。延迟 myAtomicFlag 的写入以及在此之前的任何写入直到发生某种其他形式的同步是完全合法的。
Based on the Javadoc of Unsafe (the putOrderedInt is used in the AtomicInteger.lazySet)
The backing fields in the AtomicXXX classes are volatile. The lazySet seems to write to these fields as if they are not volatile, which would remove the happens-before edges you are expecting. As noted in your link this would be useful for nulling values to be eligible for GC without having to incur the volatile write.
Edit:
This is to answer your update.
If you take a look at the quote you provided from the link then you lose any memory guarantees you had with the volatile write.
The lazySet will not be ordered above where it is being written to, but without any other actual synchronization you lose the guarantee that the consumer will see any changes that were written before it. It is perfectly legal to delay the write of the myAtomicFlag and there for any writes before it until some other form of synchronization occurs.