AtomicReferenceFieldUpdater - methods set, get, compareAndSet semantics

来自 Java AtomicReferenceFieldUpdater 文档

请注意,此类中方法的保证比其他原子类中的保证弱。由于此类无法确保字段的所有用法都适合于原子访问的目的,因此它只能相对于 和 的其他调用来保证原子性和易失性语义。compareAndSetcompareAndSetset

这意味着我不能与 一起进行正常的易失性写入,而必须改用。它没有提到任何关于.compareAndSetsetget

这是否意味着我仍然可以读取具有相同原子性保证的易失性字段 - 所有写入之前或对每个读取过易失性字段的人都可见?setcompareAndSet

还是我必须在场上使用而不是易失性读取?getAtomicReferenceFieldUpdater

如果您有参考资料,请发布参考资料。

谢谢。

编辑:

Java并发实践中,他们唯一说的话是:

更新程序类的原子性保证比常规原子类的原子性保证弱,因为您无法保证基础字段不会被直接修改 — compareAndSet 和算术方法仅保证相对于使用原子场更新程序方法的其他线程的原子性。

同样,没有提到其他线程应该如何读取这些易失性字段。

另外,我假设“直接修改”是常规的易失性写入是否正确?


答案 1

如原子的软件包文档中所述(通常,不是特定的更新程序):

原子的访问和更新的记忆效应通常遵循易失性的规则,[...]:

  • get具有读取变量的记忆效果。volatile
  • set具有写入(分配)变量的记忆效果。volatile
  • [...]
  • compareAndSet以及所有其他读取和更新操作,例如具有读取和写入变量的内存效果。getAndIncrementvolatile

原子试图解决什么问题?为什么使用(例如)而不是 ?它不是试图解决并发读取的任何问题,因为这些已经由常规.(“易失性”读取或写入与“原子”读取或写入相同。仅当并发读取发生在写入过程中,或者如果语句以某种有问题的方式重新排序或优化时,它才会成为问题;但已经阻止了这些事情。唯一解决的问题是,在这种方法中,当我们读取()和写入它()之间,其他一些线程可能会并发写入。 通过在此期间锁定任何竞争写入来解决此问题。compareAndSetatomicInteger.compareAndSet(1,2)if(volatileInt == 1) { volatileInt = 2; }volatilevolatilecompareAndSetvolatileIntvolatileIntvolatileInt == 1volatileInt = 2compareAndSet

在“更新程序”(等)的特定情况下也是如此:读取仍然只是桃色的。更新程序方法的唯一限制是,它们不是像我上面写的那样“锁定任何竞争写入”,而是只锁定来自同一个 AtomicReferenceFieldUpdater 实例的竞争写入;当您同时直接更新字段时(或者,就此而言,当您同时使用多个来更新同一字段时),它们无法保护您。(顺便说一句,取决于你如何看待它 - 同样的事情也是如此:如果你以绕过他们自己的设置者的方式更新他们的领域,他们就无法保护你。不同之处在于,一个实际上拥有它的字段,而且它是 ,所以没有必要警告你不要以某种方式通过外部手段修改它。AtomicReferenceFieldUpdatervolatilecompareAndSetvolatileAtomicReferenceFieldUpdatervolatileAtomicReferenceAtomicReferenceprivate

因此,为了回答您的问题:是的,您可以继续读取具有相同原子性的字段,以防止部分/不一致的读取,防止语句被重新排序等。volatile


编辑添加(12月6日):任何对这个主题特别感兴趣的人都可能对下面的讨论感兴趣。我被要求更新答案,以澄清该讨论的要点:

  • 我认为最重要的一点是,以上是我自己对文档的解释。我相当有信心我已经正确地理解了它,并且没有其他解释是有意义的;如果需要,我可以详细论证这一点;-) ;但是我和其他人都没有对任何权威文档的引用,这些文档比问题本身中提到的两个文档(该类的Javadoc和Java Concurrency in Practice)以及我上面对它的原始答案中提到的一个文档(包的Javadoc)更明确地解决了这一点。

  • 我认为,下一个最重要的一点是,尽管 的文档说与易失性写入混合是不安全的,但我相信在典型的平台上,它实际上是安全的。只有在一般情况下才不安全。我之所以这么说,是因为包文档中的以下注释:AtomicReferenceUpdatercompareAndSet

    这些方法的规范使实现能够采用当代处理器上可用的高效机器级原子指令。但是,在某些平台上,支持可能需要某种形式的内部锁定。因此,不能严格保证这些方法不是阻塞的 - 线程可能会在执行操作之前暂时阻塞。

    所以:

    • 在现代处理器的典型 JDK 实现中,只需使用易失性写入,因为使用相对于易失性写入的原子比较和交换操作。 必然比 更复杂,因为它必须使用类似反射的逻辑来更新另一个对象中的字段,但我坚持认为这是它更复杂的一原因。一个典型的实现调用 ,这是一个按较长名称的易失性写入。AtomicReference.setAtomicReference.compareAndSetAtomicReferenceUpdater.setAtomicReference.setUnsafe.putObjectVolatile
    • 但并非所有平台都支持这种方法,如果不支持,则允许阻止。冒着过度简化的风险,我认为这大致意味着原子类可以通过(或多或少地)应用于使用和直接使用的方法来实现。但是要使这起作用,也必须是,原因在我上面的原始答案中解释;也就是说,它不能只是一个易失性的写入,因为这样它可以在已调用之后但在调用之前修改字段。compareAndSetsynchronizedgetsetsetsynchronizedcompareAndSetgetcompareAndSetset
    • 毋庸置疑,我最初的答案使用“锁定”一词不应该从字面上理解,因为在典型的平台上,没有什么非常像锁定的需要。
  • 在 Sun 的 JDK 1.6.0_05 实现中,我们发现:java.util.concurrent.ConcurrentLinkedQueue<E>

    private static class Node<E> {
        private volatile E item;
        private volatile Node<E> next;
        private static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater =
            AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
        private static final AtomicReferenceFieldUpdater<Node, Object> itemUpdater =
            AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "item");
        Node(E x) { item = x; }
        Node(E x, Node<E> n) { item = x; next = n; }
        E getItem() { return item; }
        boolean casItem(E cmp, E val)
            { return itemUpdater.compareAndSet(this, cmp, val); }
        void setItem(E val) { itemUpdater.set(this, val); }
        Node<E> getNext() { return next; }
        boolean casNext(Node<E> cmp, Node<E> val)
            { return nextUpdater.compareAndSet(this, cmp, val); }
        void setNext(Node<E> val) { nextUpdater.set(this, val); }
    }
    

    (注意:为紧凑性调整了空格),其中,一旦构建了实例,就没有易失性写入 - 也就是说,所有写入都是通过 or - 但易失性读取似乎是自由使用的,没有一次调用。后来的 JDK 1.6 版本被更改为直接使用(Oracle 的 JDK 1.6.0_27 已经发生了这种情况),但是 JSR 166 邮件列表上的讨论将这一变化归因于性能考虑,而不是对先前实现的正确性的任何疑虑。AtomicReferenceFieldUpdater.compareAndSetAtomicReferenceFieldUpdater.setAtomicReferenceFieldUpdater.getUnsafe

    • 但我必须指出,这不是防弹权威。为了方便起见,我写了“Sun的实现”,就好像它是一个统一的东西一样,但我之前的要点清楚地表明,针对不同平台的JDK实现可能必须以不同的方式做事。在我看来,上面的代码似乎是以平台中立的方式编写的,因为它避开了普通的易失性写入,转而支持对 ;但是不接受我对一点的解释的人可能不接受我对另一点的解释,并且可能会争辩说上述代码并不意味着对所有平台都是安全的。AtomicReferenceFieldUpdater.set
    • 这个权限的另一个弱点是,虽然似乎允许不稳定读取与调用同时进行,但它是一个私有类;而且我没有采取任何证据证明其所有者()实际上在没有自己的预防措施的情况下拨打此类电话。(但是,尽管我还没有证明这一说法,但我怀疑是否有人会对此提出异议。NodeAtomicReferenceFieldUpdater.compareAndSetConcurrentLinkedQueue

有关本附录的背景和进一步讨论,请参阅以下评论。


答案 2

这意味着对该对象的引用将得到保证,但由于您可以使用任何对象,因此当另一个线程访问该对象时,可能无法正确写入该对象的字段。

唯一可以保证的方法是字段是最终的还是可变的。


推荐