必须用 compareAndSet 配合 while 循环实现原子性检查与更新,因 if + set 非原子,会导致多线程下重复重置或跳号;compareAndSet 以旧值为预期,失败即重试,确保「读—判—改」整体原子。
用 while 循环配合 atomiclong 实现序号溢出重置,核心是「原子性地检查并更新」——即在递增前判断是否已达上限,若达到则尝试重置为起始值;若重置时发现已有其他线程抢先完成,则重新参与下一轮竞争。
为什么不能直接用 if + set?
if (current == MAX) { counter.set(START); } 是非原子操作,多线程下存在竞态:两个线程同时读到 current == MAX,都执行 set(START),导致跳号或重复重置。必须把「读—判—改」封装成一个原子步骤。
用 compareAndSet 配合 while 实现安全重置
典型模式是:循环读取当前值 → 判断是否溢出 → 若未溢出则尝试递增;若已溢出则尝试重置。关键在于每次 compareAndSet 都以「刚读到的旧值」为预期,失败就重试。
示例逻辑(起始值 1,上限 1000):
private static final long START = 1L;
private static final long MAX = 1000L;
private final AtomicLong counter = new AtomicLong(START);
public long nextId() {
while (true) {
long current = counter.get();
if (current
注意边界与语义一致性
-
MAX 的含义要明确:上例中
MAX == 1000表示最大允许值,生成1000后下一次重置为1;若希望支持到1000后再发一次1000才重置,需调整判断条件(如current >= MAX) -
避免无限等待:该循环在高并发下可能多次重试,但
AtomicLong.compareAndSet是无锁且高效的操作,实际压测中极少出现长延迟 -
不保证严格连续:重置瞬间可能有多个线程拿到相同新值(比如都看到刚重置的
1并成功 CAS 为2),但这是正常现象——只要 ID 不重复、单调递增(重置后从头开始也算单调段),就满足多数场景需求
可选增强:带版本或时间戳的防回绕
纯序号重置存在「ID 回绕」风险(例如 999→1),若业务要求全局绝对唯一且不可重复,建议组合时间戳(如 (timestamp ),此时序号部分只需按位循环(如 0~4095),用 <code>& 0x3FF 替代比较判断,更轻量:
private final AtomicLong seq = new AtomicLong();
private static final long SEQ_MASK = 0x3FFL; // 10 位,0~1023
public long nextSequence() {
return seq.getAndIncrement() & SEQ_MASK;
}
这种位运算方式无需分支和 CAS 重试,性能更高,适合高频短周期序号场景。










