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 可能比 set
更快
- lazySet 使用 store-store barrier(之前的写入被接受但竞争的写入不被接受,这还没有发生)
我可以从文档中找到一个可以应用它的用例:
- 当你想清空一个指针来帮助 GC 时使用 lazySet。
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写快很多?
set
和 lazySet
之间的主要区别是没有 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 论文。
来自这个问题:AtomicInteger lazySet vs. set and form this link : https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html
我可以收集以下几点
- lazySet 可能比 set 更快
- lazySet 使用 store-store barrier(之前的写入被接受但竞争的写入不被接受,这还没有发生)
我可以从文档中找到一个可以应用它的用例:
- 当你想清空一个指针来帮助 GC 时使用 lazySet。
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()
.
为什么比volatile写快很多?
set
和 lazySet
之间的主要区别是没有 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 论文。