高多线程环境下安全重置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
相关产品推荐
相关产品推荐

