Java Spring Boot中用System.currentTimeMillis()作唯一ID是否会引发竞态条件?如何生成短唯一ID?
关于Java Spring Boot中唯一ID的问题解答
1. 使用System.currentTimeMillis()作为唯一ID是否会导致竞态条件?
会的。System.currentTimeMillis()返回当前时间的毫秒级戳,同一毫秒内的多个请求会生成完全相同的ID,这直接引发竞态条件——比如数据库主键冲突、业务逻辑中ID重复导致的异常。
发生概率
概率完全由系统并发量决定:
- 低并发场景(QPS<100):同一毫秒出现多请求的概率极低,但并非为零,仍存在潜在风险。
- 高并发场景(QPS>1000):每秒1000次请求意味着平均每毫秒1次请求,QPS达10000时每毫秒会有10次请求,此时ID重复概率极高,绝对不能用这种方式生成唯一ID。
2. 生成16-18字符唯一ID的可行方法
以下是几种适配Java Spring Boot项目的实现方案,无需依赖外部服务:
方案一:时间戳+原子自增序列(单实例场景)
利用13位毫秒时间戳,搭配3-5位原子自增序列,总长度控制在16-18位,保证同一毫秒内ID唯一。
import java.util.concurrent.atomic.AtomicInteger; public class ShortUniqueIdGenerator { // 原子序列保证线程安全 private static final AtomicInteger SEQUENCE = new AtomicInteger(0); private static long LAST_TIMESTAMP = -1; // 生成18位唯一ID:13位时间戳 + 5位序列 public static String generate18BitId() { long currentTimestamp = System.currentTimeMillis(); if (currentTimestamp == LAST_TIMESTAMP) { // 同一毫秒内序列自增,超过99999则重置 int seq = SEQUENCE.incrementAndGet() % 100000; return currentTimestamp + String.format("%05d", seq); } else { // 进入新毫秒,重置序列 LAST_TIMESTAMP = currentTimestamp; SEQUENCE.set(0); return currentTimestamp + "00000"; } } // 生成16位唯一ID:13位时间戳 + 3位序列 public static String generate16BitId() { long currentTimestamp = System.currentTimeMillis(); if (currentTimestamp == LAST_TIMESTAMP) { int seq = SEQUENCE.incrementAndGet() % 1000; return currentTimestamp + String.format("%03d", seq); } else { LAST_TIMESTAMP = currentTimestamp; SEQUENCE.set(0); return currentTimestamp + "000"; } } }
方案二:时间戳+机器标识+序列(分布式场景)
多实例部署时,需加入机器/实例标识避免跨实例ID重复,比如取IP后两位、进程ID后两位,结合时间戳和序列:
import java.net.InetAddress; import java.util.concurrent.atomic.AtomicInteger; public class DistributedShortIdGenerator { private static final AtomicInteger SEQUENCE = new AtomicInteger(0); private static long LAST_TIMESTAMP = -1; // 2位机器标识,取IP最后一段的后两位,异常时用随机数兜底 private static final String MACHINE_ID; static { try { String ip = InetAddress.getLocalHost().getHostAddress(); String[] segments = ip.split("\\."); int lastSegment = Integer.parseInt(segments[3]); MACHINE_ID = String.format("%02d", lastSegment % 100); } catch (Exception e) { MACHINE_ID = String.format("%02d", (int) (Math.random() * 100)); } } // 生成18位ID:13位时间戳 + 2位机器ID + 3位序列 public static String generate() { long currentTimestamp = System.currentTimeMillis(); if (currentTimestamp == LAST_TIMESTAMP) { int seq = SEQUENCE.incrementAndGet() % 1000; return currentTimestamp + MACHINE_ID + String.format("%03d", seq); } else { LAST_TIMESTAMP = currentTimestamp; SEQUENCE.set(0); return currentTimestamp + MACHINE_ID + "000"; } } }
方案三:变种雪花算法转十进制
调整雪花算法结构后转十进制字符串,可控制长度在16-18位:
- 时间戳部分:41位(对应约69年毫秒级时间),转十进制为13位
- 机器ID:5位(支持32台机器),转十进制为1-2位
- 序列ID:12位(每毫秒最多4096个序列),转十进制为1-4位
组合后总长度在18位以内,既保证分布式唯一,又满足长度要求。
注意事项
- 单实例方案禁止用于分布式场景,否则多实例同一毫秒生成的ID会重复。
- 若对ID有序性有要求,优先选择带时间戳的方案,便于后续排序和问题排查。
- 避免纯随机数方案,虽长度可控,但重复概率远高于时间戳+序列的组合。
内容的提问来源于stack exchange,提问作者uniQ
相关产品推荐
相关产品推荐

