lazyset仅适用于写频繁、读不敏感、更新后不要求立即可见的场景;它只保证写顺序不重排,不保证其他线程即时看到新值,不可用于同步协调或通知等待。

lazySet 什么时候能用?看三个硬条件
能用 lazySet 的前提是:写操作频繁、读操作不敏感、且更新后**不要求其他线程立刻感知**。它不是“慢一点的 set”,而是语义上就放弃即时可见性的操作。
常见误用场景就是把它当 set 的平替——比如在状态机里用 status.lazySet(1) 表示“已启动”,然后另一个线程马上轮询 status.get() == 1 等结果,这就极可能卡死或延迟数毫秒到数秒(取决于 CPU 缓存同步时机)。
- ✅ 单生产者、多消费者模型中更新尾指针(如 RingBuffer)
- ✅ 日志计数器、监控指标累加(最终一致即可)
- ✅ 取消任务时标记“正在关闭”,后续有独立检查逻辑兜底
- ❌ 作为锁状态、信号量、双重检查锁中的 volatile 替代
- ❌ 用于线程间“通知-等待”协作(没 happens-before,等同于裸变量)
lazySet 和 set 到底差在哪?不是快慢,是内存屏障类型
set() 是标准 volatile 写,插入 StoreStore + StoreLoad 屏障,强制刷缓存、禁止重排序、建立 happens-before;lazySet() 只插入 StoreStore 屏障,仅防“自己前面的写被重排到它后面”,不刷缓存,也不管别人看不看得见。
这导致一个关键差异:即使你调用了 lazySet(42),其他线程执行 get() 仍可能读到旧值,直到发生某次同步事件(比如它自己也执行了 volatile 读/写、进入 synchronized 块、或碰巧 CPU 缓存行被 invalidate)。
-
set():强一致性,开销高,适合控制流关键点 -
lazySet():弱有序性,低开销,只保“顺序”,不保“可见” - 底层对应 JVM 指令:
set→putVolatileInt,lazySet→putOrderedInt
AtomicInteger.lazySet 的典型错误现象
最常遇到的问题不是“不工作”,而是“看起来工作,但偶尔卡住”——尤其在测试环境跑得飞快,上线后在高负载多核机器上复现。
比如这段代码:
AtomicInteger flag = new AtomicInteger(0);
// 线程 A
new Thread(() -> {
doWork();
flag.lazySet(1); // ❌ 这里改了,但线程 B 可能永远看不到
}).start();
<p>// 线程 B
new Thread(() -> {
while (flag.get() == 0) { /<em> spin </em>/ } // ⚠️ 可能死循环
System.out.println("done");
}).start();原因不是 bug,是语义违规:用 lazySet 发布信号,却指望 get() 立即收到。修复很简单——把 lazySet(1) 换成 set(1),或者让线程 B 改用 await 类机制。
AtomicReference.lazySet 为什么比 AtomicInteger.lazySet 更常用?
因为对象引用的发布场景天然更适合 lazySet 的语义:比如往无锁队列尾部添加节点,只需要保证“我写的 next 指针不会被重排到构造节点之前”,并不需要其他线程立刻看到这个新节点——它们自然会在后续遍历中碰到。
AtomicInteger 多用于计数或状态码,人容易直觉认为“设了就得马上生效”;而 AtomicReference.lazySet 在 LMAX Disruptor、JCTools 等高性能库中大量出现,恰恰因为它和数据结构操作的弱一致性需求更匹配。
- 推荐优先考虑
AtomicReference.lazySet而非AtomicInteger.lazySet - 如果真要用
AtomicInteger.lazySet,确保它的用途是“单向发布+异步消费”,而非“同步协调” - 别被名字误导:
lazySet不是“懒”,是“不承诺可见”
真正难的不是怎么写,是怎么判断某个字段是否属于“可容忍延迟可见”的那一类——这需要对整个并发流程的同步契约有清晰认知,而不是查文档抄个方法名就完事。











