Java 在 AtomicXXX 上使用 lazySet

Usage of lazySet on AtomicXXX in Java

来自这个问题:AtomicInteger lazySet vs. set and form this link : https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html

我可以收集以下几点

我可以从文档中找到一个可以应用它的用例:

lazySet 还有其他实际用例吗?

我认为 AtomicBoolean 的许多用途会受益于 lazySet() 的使用,因为它们通常用作标志来指示某事是否完成,或者外部循环是否应该完成。

这是因为在这种情况下,该值最初是一个值,最终变成另一个值,然后停留在那里。显然,这个论点适用于几乎所有以这种方式使用的原子。

public void test() {
    final AtomicBoolean finished = new AtomicBoolean(false);
    new Thread(new Runnable() {

        @Override
        public void run() {
            while (!finished.get()) {
                // A long process.
                if (wereAllDone()) {
                    finished.lazySet(true);
                }
            }
        }

    }).start();
}

TL;DR 如何使用.lazySet()?小心点,如果有的话。

这里的主要问题是 AtomicXXX.lazySet() 是低级性能优化,它超出了当前的 JLS。如果您使用的是 lazySet().

,那么如果您使用 JMM 工具并发代码,则无法证明正确性

为什么比volatile写快很多?

setlazySet 之间的主要区别是没有 StoreLoad 屏障。

JSR-133 Cookbook for Compiler Writers:

StoreLoad barriers are needed on nearly all recent multiprocessors, and are usually the most expensive kind.

此外,在最流行的基于 x86 的硬件上,StoreLoad 是唯一的显式屏障(其他的只是空操作并且没有任何成本),因此使用 lazySet 可以消除所有(显式)内存屏障。

lazySet 的保证

从 JLS 的角度来看,没有。 但实际上你可以将 lazySet 推理为 delayed 写入,它不能用任何先前的写入重新排序并且最终会发生。 Eventually 是有限的时间,如果您的进程取得任何进展(例如任何 synchronization action 发生;此外,处理器存储缓冲区的大小是有限的)。如果写入的值对其他线程可见,您可以确定所有以前的写入也是可见的(尽管您无法正式证明这一点)。所以你可以把它当作延迟发生前的关系(但是,当然,它甚至不接近它的严格和正式定义)。

用法

最实际的用法(清零引用除外)是在进度上下文中使写入成本低得多。最简单的例子是在 synchronized 块中使用 lazySet() 而不是 set() (但在这种情况下没有很大的性能影响)。或者,当没有写入竞争发生时,您可以在单一生产者情况下使用它而不是写入。 Disruptor 开发人员在他们的无锁实现中正是出于这个目的使用 lazySet。同样,很难争论此类代码的正确性,但这是一个需要注意的好技巧。

Caffeine 在其许多数据结构中使用惰性或宽松写入。

  • 清空字段时(例如 ConcurrentLinkedStack)
  • 在发布前写入可变字段时(例如 SingleConsumerQueue)
  • 何时可以安全地延迟发布(例如 BoundedBuffer)
  • 当竞争是良性的(例如缓存过期时间戳)
  • 在锁内(例如 BoundedLocalCache)

ConcurrentLinkedQueue 在发布节点之前使用宽松的写入,并且可能延迟设置节点的下一个字段(在发布之前或指示过时的遍历)。

您可能还喜欢阅读 Linux Kernel Memory Barriers 论文。