不能直接用 volatile boolean 做标志位,因其不保证复合操作原子性,如 flag = !flag 会因读-改-写三步分离导致状态丢失;synchronized(Boolean) 也无效,因 Boolean 对象不可变;AtomicBoolean 的 compareAndSet 才能安全实现启停控制。

为什么不能直接用 volatile boolean 做标志位
volatile 能保证可见性,但不保证复合操作的原子性。比如 flag = !flag 看似简单,实际分三步:读取当前值 → 取反 → 写回。多线程下可能两个线程同时读到 true,都取反为 false,最终都写回 false,丢失一次状态翻转。
更隐蔽的问题是:用 synchronized(flag) 锁布尔包装类也无效——因为 Boolean.TRUE 和 Boolean.FALSE 是不同对象,锁住一个后赋值会指向另一个对象,锁就失效了。
AtomicBoolean.compareAndSet 是最安全的启停控制方式
它把“检查旧值 + 设置新值”封装成一条不可分割的 CPU 指令(CAS),天然避免竞态。典型用法是做线程任务的启停开关:
- 启动时用
compareAndSet(false, true),确保只启动一次 - 运行中用
get()判断是否继续循环 - 停止时用
set(false)或lazySet(false)(后者性能略高,但允许短暂延迟可见)
示例片段:
private final AtomicBoolean running = new AtomicBoolean(false);
public void start() {
if (running.compareAndSet(false, true)) {
new Thread(this::doWork).start();
}
}
private void doWork() {
while (running.get()) {
// 执行业务逻辑
try { Thread.sleep(100); } catch (InterruptedException e) { break; }
}
}
public void stop() {
running.set(false);
}
AtomicBoolean 和 volatile boolean 的性能与语义差异
AtomicBoolean 底层其实也是靠 volatile int 实现的(true 映射为 1,false 为 0),所以单次 get() / set() 开销和 volatile boolean 几乎一致。真正区别在 compareAndSet() 这类原子操作:
- 需要严格状态切换逻辑(如“仅首次启动”“仅首次关闭”)→ 必须用
AtomicBoolean - 只是单向通知(如“全局开关已关闭”,之后不再改回)→
volatile boolean足够,且更轻量 - 频繁读、极少写的标志位 →
lazySet()比set()更适合,避免写屏障开销
容易被忽略的内存模型细节
即使用了 AtomicBoolean,如果任务逻辑里有非原子的共享变量访问,依然可能出错。比如:
- 标志位设为
false后,线程仍可能在退出前读到过期的缓存数据 - 没有对关键临界区加同步,仅靠标志位无法保护数据一致性
- 忘记在
catch (InterruptedException)中主动退出循环,导致running.get()永远为true
标志位只解决“要不要继续跑”的问题,不解决“跑的过程中数据怎么不乱”。真正健壮的并发控制,往往要组合使用 AtomicBoolean + 显式同步 + 正确的中断响应。

















