AtomicReferenceFieldUpdater - 方法 set、get、compareAndSet 语义
来自 Java AtomicReferenceFieldUpdater文档
:
请注意,此类中的
compareAndSet
方法的保证是 比其他原子类弱。因为这个类不能保证 该字段的所有用途都适合原子目的 访问,它只能保证原子性和易失性语义 尊重compareAndSet
和set
的其他调用。
这意味着我无法与 compareAndSet
一起进行正常的易失性写入,而必须使用 set
来代替。它没有提及任何有关 get
的内容。
这是否意味着我仍然可以读取具有相同原子性保证的易失性字段 - 在 set
或 compareAndSet
之前的所有写入对于读取易失性字段的每个人都是可见的?
或者我是否必须在 AtomicReferenceFieldUpdater
上使用 get
而不是字段上的易失性读取?
如果您有参考文献,请发布。
谢谢。
编辑:
来自 Java 并发实践,他们唯一说的是:
更新器类的原子性保证比 常规原子类,因为你不能保证 底层字段不会被直接修改——compareAndSet 和算术方法仅保证其他方面的原子性 使用原子字段更新器方法的线程。
同样,没有提及其他线程应该如何读取这些易失性字段。
另外,我是否正确地假设“直接修改”是常规的易失性写入?
From the Java AtomicReferenceFieldUpdater
docs:
Note that the guarantees of the
compareAndSet
method in this class are
weaker than in other atomic classes. Because this class cannot ensure
that all uses of the field are appropriate for purposes of atomic
access, it can guarantee atomicity and volatile semantics only with
respect to other invocations ofcompareAndSet
andset
.
This means I can't do normal volatile writes along with compareAndSet
, but have to use set
instead. It doesn't mention anything about get
.
Does that mean that I can still read volatile fields with the same atomicity guarantees - all writes before the set
or compareAndSet
are visible to everybody who has read the volatile field being?
Or do I have to use get
on the AtomicReferenceFieldUpdater
instead of volatile reads on the field?
Please post references if you have them.
Thank you.
EDIT:
From Java Concurrency in Practice, the only thing they say:
The atomicity guarantees for the updater classes are weaker than for
the regular atomic classes because you cannot guarantee that the
underlying fields will not be modified directly — the compareAndSet
and arithmetic methods guarantee atomicity only with respect to other
threads using the atomic field updater methods.
Again, no mention of how the other threads are supposed to read these volatile fields.
Also, am I right to assume that "modified directly" is a regular volatile write?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
正如原子的包文档中所述(一般来说,不是特定的更新程序):
原子的
compareAndSet
试图解决什么问题?为什么使用(例如)atomicInteger.compareAndSet(1,2)
而不是if(volatileInt == 1) { volatileInt = 2; }
?它不尝试解决并发读取的任何问题,因为这些问题已经由常规易失性
处理。 (“易失性”读取或写入与“原子”读取或写入相同。并发读取只有在写入过程中发生,或者语句以某种有问题的方式重新排序或优化时才会出现问题;但是volatile
已经阻止了这些事情。)compareAndSet
解决的唯一问题是,在volatileInt
方法中,其他线程可能会出现并发写入,之间当我们读取volatileInt
(volatileInt == 1
) 和写入它 (volatileInt = 2
) 时。compareAndSet
通过锁定这段时间内任何竞争的写入来解决这个问题。在“更新程序”(
AtomicReferenceFieldUpdater
等)的特定情况下也是如此:易失性
读取仍然很理想。更新程序的compareAndSet
方法的唯一限制是,它们不是像我上面写的那样“锁定任何竞争写入”,而是仅锁定来自AtomicReferenceFieldUpdater
的同一实例;当您同时直接更新 volatile 字段时(或者,当您同时使用多个 AtomicReferenceFieldUpdater 来更新相同的字段时)它们无法保护您易失性
字段)。 (顺便说一句,这取决于您如何看待它 -AtomicReference
及其同类也是如此:如果您以绕过其自己的 setter 的方式更新其字段,它们将无法保护您。不同之处在于AtomicReference
实际上拥有其字段,并且它是private
,因此无需警告您不要通过外部方式对其进行修改。)因此,回答你的问题:是的,你可以继续读取具有相同原子性保证的
易失性
字段,以防止部分/不一致读取、防止语句重新排序等。编辑添加(12月6):任何对此主题特别感兴趣的人可能会对下面的讨论感兴趣。我被要求更新答案,以澄清该讨论中的要点:
我认为要添加的最重要的一点是,上述内容是我自己对文档的解释。我相当有信心我已经正确理解了它,并且没有其他解释有意义;如果需要的话,我可以详细论证这一点;-);但我和其他任何人都没有提供任何权威文档的引用,比问题本身中提到的两个文档(该类的 Javadoc 和 Java Concurrency in Practice)和一个文档更明确地解决了这一点我在上面的原始答案中提到的文档(包的 Javadoc)。
我认为下一个最重要的一点是,尽管
AtomicReferenceUpdater
的文档说将compareAndSet
与易失性写入混合使用是不安全的,但我相信在典型情况下平台实际上是安全的。仅在一般情况下才是不安全的。我这样说是因为包文档中的以下评论:<块引用>
这些方法的规范使实现能够采用当代处理器上可用的高效机器级原子指令。然而,在某些平台上,支持可能需要某种形式的内部锁定。因此,不能严格保证这些方法是非阻塞的——线程在执行操作之前可能会暂时阻塞。
所以:
AtomicReference.set
仅使用易失性写入,因为AtomicReference.compareAndSet
使用比较和交换操作相对于易失性写入而言是原子的。AtomicReferenceUpdater.set
必然比AtomicReference.set
更复杂,因为它必须使用类似反射的逻辑来更新另一个对象中的字段,但我认为这就是只是因为它更复杂。典型的实现调用Unsafe.putObjectVolatile
,这是一个较长名称的易失性写入。synchronized
应用到使用的方法来实现直接获取和设置。但要使其发挥作用,
set
还必须synchronized
,原因在我上面的原始答案中已解释过;也就是说,它不能只是一个易失性写入,因为这样它就可以在compareAndSet
调用get
之后但在compareAndSet
调用之前修改该字段设置
。在 Sun 的 JDK 1.6.0_05 的
java.util.concurrent.ConcurrentLinkedQueue
实现中,我们发现:(注意:为紧凑性调整了空格),其中,一旦构造了实例,就没有易失性写入 - 也就是说,所有写入都是通过
AtomicReferenceFieldUpdater.compareAndSet
或AtomicReferenceFieldUpdater 进行的。 set
- 但易失性读取似乎可以自由使用,无需调用AtomicReferenceFieldUpdater.get
。 JDK 1.6 的后续版本更改为直接使用Unsafe
(Oracle 的 JDK 1.6.0_27 就发生了这种情况),但 JSR 166 邮件列表上的讨论将这一更改归因于性能考虑,而不是任何关于先前实现的正确性。AtomicReferenceFieldUpdater.set
;但是不接受我对这一点的解释的人可能不会接受我对另一点的解释,并且可能会争辩说上面的代码不意味着对所有平台都是安全的。ConcurrentLinkedQueue
) 实际上在没有采取任何预防措施的情况下进行此类调用。 (虽然我还没有证明这一说法,但我怀疑有人会对此提出异议。)请参阅下面的评论以了解本附录的背景以及进一步的讨论。
As explained in the package documentation for atomics (in general, not the updaters specifically):
What problem is an atomic's
compareAndSet
trying to solve? Why use (for example)atomicInteger.compareAndSet(1,2)
instead ofif(volatileInt == 1) { volatileInt = 2; }
? It's not trying to solve any problem with concurrent reads, because those are already taken care of by a regularvolatile
. (A "volatile" read or write is the same as an "atomic" read or write. A concurrent read would only be a problem if it happened in the middle of a write, or if statements were reordered or optimized in some problematic way; butvolatile
already prevents those things.) The only problem thatcompareAndSet
solves is that, in thevolatileInt
approach, some other thread might come in with a concurrent write, between when we readvolatileInt
(volatileInt == 1
) and when we write to it (volatileInt = 2
).compareAndSet
solves this problem by locking out any competing writes during that time.This is equally true in the specific case of the "updaters" (
AtomicReferenceFieldUpdater
etc.):volatile
reads are still just peachy. The updaters'compareAndSet
methods' only limitation is that, instead of "locking out any competing writes" as I wrote above, they only lock out competing writes from the same instance ofAtomicReferenceFieldUpdater
; they can't protect you when you're concurrently updating avolatile
field directly (or, for that matter, when you're concurrently using multipleAtomicReferenceFieldUpdater
s to update the samevolatile
field). (Incidentally, depending how you look at it — the same is true ofAtomicReference
and its kin: if you were to update their fields in a way that bypassed their own setters, they couldn't protect you. The difference is that anAtomicReference
actually owns its field, and it'sprivate
, so there's no need to warn you against somehow modifying it by external means.)So, to answer your question: Yes, you can continue to read
volatile
fields with the same atomicity guarantees against partial/inconsistent reads, against statements being reordered, etc.Edited to add (Dec 6): Anyone who's particularly interested in this subject will probably be interested in the discussion immediately below. I was asked to update the answer to clarify salient points from that discussion:
I think the most important point to add is that the above is my own interpretation of the documentation. I'm fairly confident that I have understood it correctly, and that no other interpretation makes sense; and I can, if desired, argue the point at length ;-) ; but neither I nor anyone else has produced any references to any authoritative document that addresses this point any more explicitly than the two documents mentioned in the question itself (the class's Javadoc and Java Concurrency in Practice) and the one document mentioned in my original answer to it above (the package's Javadoc).
The next most important point, I think, is that although the documentation for
AtomicReferenceUpdater
says that it's unsafe to mixcompareAndSet
with a volatile write, I believe that on typical platforms it actually is safe. It's unsafe only in the general case. I say this because of the following comment from the package documentation:So:
AtomicReference.set
simply uses a volatile write, sinceAtomicReference.compareAndSet
uses a compare-and-swap operation that is atomic with respect to volatile writes.AtomicReferenceUpdater.set
is necessarily more complex thanAtomicReference.set
, because it has to use reflection-like logic to update a field in another object, but I maintain that that is the only reason it is more complex. A typical implementation callsUnsafe.putObjectVolatile
, which is a volatile write by longer name.compareAndSet
could be implemented by (more or less) applyingsynchronized
to a method that usesget
andset
straightforwardly. But for this to work,set
must also besynchronized
, for the reason explained in my original answer above; that is, it can't just be a volatile write, because then it could modify the field aftercompareAndSet
has calledget
but beforecompareAndSet
callsset
.In Sun's JDK 1.6.0_05 implementation of
java.util.concurrent.ConcurrentLinkedQueue<E>
, we find this:(note: whitespace adjusted for compactness), where, once an instance has been constructed, there are no volatile writes — that is, all writes are via
AtomicReferenceFieldUpdater.compareAndSet
orAtomicReferenceFieldUpdater.set
— but volatile reads appear to be used freely, without a single call toAtomicReferenceFieldUpdater.get
. Later releases of JDK 1.6 were changed to useUnsafe
directly (this had happened by Oracle's JDK 1.6.0_27), but discussions on the JSR 166 mailing list attribute this change to performance considerations rather than to any qualm about the correctness of the previous implementation.AtomicReferenceFieldUpdater.set
; but someone who doesn't accept my interpretation of the one point may not accept my interpretation of the other, and might argue that the above code is not meant to be safe for all platforms.Node
seems to allow volatile reads to take place concurrently with calls toAtomicReferenceFieldUpdater.compareAndSet
, it's a private class; and I have not undertaken any proof that its owner (ConcurrentLinkedQueue
) actually makes such calls without its own precautions. (But although I have not proven the claim, I doubt that anyone would dispute it.)Please see the below comments for background on this addendum, and for further discussion.
这意味着对对象的引用将得到保证,但由于您可以使用任何对象,因此当另一个线程访问该对象时,该对象的字段可能无法正确写入。
唯一可以保证的方法是这些字段是最终的还是可变的。
What this means is that the reference to the object will be guaranteed but because you can use any object, the fields of that object may not be properly written when another thread goes to access the object.
The only way that could be guaranteed is if the fields were final or volatile.
这不是问题的准确答案:
从文档中看,无论是解释还是意图都不清楚。如果这个想法是绕过允许它的架构上的全局排序(即易失性写入)[如 IBM Power 或 ARM],并且仅公开 CAS(LoadLinked/StoreCondition)行为而不进行防护,那么这将是一项相当惊人的工作,也是造成混乱的根源。
sun.misc.Unsafe 的 CAS 没有规范或顺序保证(之前发生过),但 java.util.atomic... 有。所以在较弱的模型 java.util.atomic impl 上。在这种情况下,需要必要的围栏来遵循 java 规范。
假设更新程序类实际上缺少栅栏。
如果他们这样做,字段的易失性读取(不使用 get)将返回更新值,即显然不需要
get()
。由于没有订购保证,以前的商店可能不会传播(在弱模型上)。在 x86/Sparc TSO 硬件上确保 java 规范。然而,这也意味着 CAS 可以通过以下非易失性读取进行重新排序。
java.util.concurrent.SynchronousQueue 队列中有一个有趣的注释:
提到的所有原子操作都是 AtomicReferenceFieldUpdater 的 CAS。这意味着正常读取和写入与 AtomicReferenceFieldUpdater.CAS 之间缺少或重新排序,即行为类似于易失性写入。
只有 CAS,没有易失性写入。
鉴于上述条件,我得出的结论是 AtomicXXXFieldUpdater 公开了与其 AtomicXXX 对应项相同的语义。
This will not be an exact answer of the question:
Neither explanation, nor intent looks clear from the documentation. If the idea was to bypass global ordering aka volatile write on architectures that allow it [like IBM Power or ARM] and just expose CAS (LoadLinked/StoreCondition) behavior WITHOUT fencing, it'd quite an amazing effort and source of confusion.
sun.misc.Unsafe's CAS has no specification or ordering guarantees (known as happens before) but java.util.atomic... does. So on weaker model java.util.atomic impl. would require necessary fences to follow java specification in this case.
Assuming Updater classes actually lack the fences.
If they do so, volatile read of field (w/o using get) shall return the update value, i.e. clearly
get()
is unneeded. Since there won't be ordering guarantees, the previous stores might not be propagate (on weak models). On x86/Sparc TSO hardware ensures java spec.However, that also means CAS can be reordered with following non-volatile reads.
There is an interesting note from
java.util.concurrent.SynchronousQueue
queue:All atomic operation mentioned are exactly CAS of AtomicReferenceFieldUpdater. That would imply the lack or reording between normal reads AND writes and AtomicReferenceFieldUpdater.CAS, i.e. acting like volatile write.
Just CAS, no volatile writes.
Given the condition above, I'd conclude the AtomicXXXFieldUpdater expose the same semantics as their AtomicXXX counterparts.