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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 18:13:24