You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

高多线程环境下安全重置AtomicInteger值及生成唯一序列ID的实现疑问

多线程环境下安全重置AtomicInteger值及生成唯一序列ID的实现疑问

最近在做一个高并发场景下的唯一序列ID生成工具,需求很明确:

  • 序列号范围是0000到9999,每次到9999后自动重置为0
  • 重置的同时,重置计数(resetCount)加1
  • 最终生成的ID格式是「重置计数-序列号」,必须保证绝对唯一

最开始我用两个AtomicInteger来维护状态,写了第一版代码:

public class SequenceGenerator {

   private static final SequenceGenerator GENERATOR = new SequenceGenerator();
   private final AtomicInteger sequence = new AtomicInteger(0);
   private final AtomicInteger resetCount = new AtomicInteger(0);

   private SequenceGenerator() {}

   public static SequenceGenerator getInstance() {
      return GENERATOR;
   }

   /**
    * 生成唯一序列ID,格式为[ResetCount-SequenceNumber]
    * 序列号范围是[0000,9999],超过9999后重置为0,同时ResetCount加1
    * @return 格式化后的唯一序列ID
   */
   public String generateSequenceId() {
    
      while (true) {
        
        int local = sequence.getAndIncrement();
         
        if (local <= 9999) {                
            // 生成并返回格式正确的ID
            return resetCount + "-" + local;
            
        } else {
            // 当序列数超过4位时执行重置逻辑
            // 只有拿到local=10000的线程才应该重置sequence为0
            if(local == 10000){
                sequence.set(0);
                resetCount.incrementAndGet();
            }
        }
    }
  }
}

写完后才反应过来,这里有个核心问题:涉及多个AtomicInteger的复合操作(先重置sequence,再递增resetCount)并不是线程安全的——高并发下可能有多个线程进入else分支,导致重置逻辑混乱,甚至生成重复ID。

后来参考了讨论里的建议,我给重置逻辑加了一个独占锁,更新了代码:

public class SequenceGenerator {

   private static final SequenceGenerator GENERATOR = new SequenceGenerator();
   private final AtomicInteger sequence = new AtomicInteger(0);
   private final AtomicInteger resetCount = new AtomicInteger(0);
   private final Object resetLock = new Object();

   private SequenceGenerator() {}

   public static SequenceGenerator getInstance() {
      return GENERATOR;
   }

   /**
    * 生成唯一序列ID,格式为[ResetCount-SequenceNumber]
    * 序列号范围是[0000,9999],超过9999后重置为0,同时ResetCount加1
    * @return 格式化后的唯一序列ID
   */
   public String generateSequenceId() {
    
      while (true) {
        
        int local = sequence.getAndIncrement();
         
        if (local <= 9999) {                
            return resetCount + "-" + local;
            
        } else {
            // 进入重置分支后,先获取独占锁
            synchronized(resetLock){
                // 双重检查sequence状态,避免重复重置
                if(sequence.get() >= 10000){
                    sequence.set(0);
                    resetCount.incrementAndGet();
                }
            }
        }
    }
  }
}

现在的逻辑是,只有拿到resetLock的线程才能执行重置,而且重置前会再检查一次sequence的状态,避免多个线程重复触发重置。不过我还是有点拿不准:当一个线程在执行sequence.set(0)的时候,另一个线程调用sequence.getAndIncrement(),会不会出现竞态条件?比如刚把sequence设为0,另一个线程的getAndIncrement会不会拿到错误的值,导致生成重复的ID?

另外补充一下,之前意识到复合原子操作(多个原子变量的连续操作)本身不具备线程安全性,这点要特别感谢@Solomon Slow的提醒。


备注:内容来源于stack exchange,提问作者Awan Biru

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 12:04:30