system.currenttimemillis() 是生成业务流水号的时间基底而非直接流水号,需结合前缀、格式化时间字符串和序列号拼接;推荐“前缀+yyyymmddhhmmsssss+三位序列号”方式,兼顾可读性、唯一性与扩展性。

System.currentTimeMillis() 返回的是自 1970-01-01 00:00:00 UTC 起的毫秒数,本身不是流水号,但可以作为高并发下生成唯一、有序、可读业务流水号的核心时间基底。关键在于:不能直接用它当流水号(会重复、无业务含义、长度不固定),而要结合其他字段做拼接和增强。
加前缀 + 时间戳 + 序列号(推荐)
这是最常用且实用的方式,兼顾可读性、唯一性、顺序性和扩展性:
- 前缀:标识业务类型,比如 "ORD"(订单)、"PAY"(支付)、"REF"(退款),便于日志追踪和数据库分表
-
时间部分:用
System.currentTimeMillis()或更简洁的System.nanoTime()不合适(非绝对时间),建议用格式化时间字符串,如yyyyMMddHHmmssSSS(精确到毫秒),既可读又保持时序 -
序列号:同一毫秒内可能产生多笔,需本地计数器(如
AtomicInteger)或分布式 ID 生成器(如雪花 ID 的序列位)。单机场景下,每毫秒重置计数器即可
示例代码(单机轻量版):
private static final AtomicInteger seq = new AtomicInteger(0);
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
public static String generateOrderNo() {
String timePart = LocalDateTime.now().format(FORMATTER);
int currentSeq = seq.incrementAndGet();
if (currentSeq > 999) seq.set(0); // 防溢出,也可用 %1000
return "ORD" + timePart + String.format("%03d", currentSeq);
}
// 输出类似:ORD20240520142305123001
纯数字时间戳 + 自增偏移(适合索引友好场景)
如果数据库主键或查询要求纯数字、长度固定、天然排序,可将时间戳左移后叠加序列:
- 取
System.currentTimeMillis()的后 32~36 位(保证未来几十年不溢出),左移 10~12 位留给序列号空间 - 用
AtomicLong在本毫秒内递增,与时间戳“或”运算或相加 - 最终得到一个约 13~14 位的长整型 ID,全局趋势递增,适合 MySQL InnoDB 主键
注意:该方式牺牲可读性,但对分库分表+范围查询友好,且避免 UUID 的随机写入问题。
规避常见坑
- 不要直接用 currentTimeMillis() 当流水号:多线程下极易重复,尤其在循环或高频调用中
-
别依赖服务器系统时间绝对准确:NTP 校时可能导致时间回拨,造成 ID 乱序甚至重复;生产环境建议搭配逻辑时钟(如 Twitter Snowflake 的 epoch 偏移)或使用
System.nanoTime()辅助检测回拨 -
单机序列号要重置时机合理:按毫秒重置比按秒更安全;若用
ThreadLocal管理序列,注意线程复用(如线程池)导致的污染 - 分布式部署必须升级方案:单机计数器失效,需引入机器 ID(如配置文件指定)、Redis INCR、或集成 Leaf、TinyID 等中间件
简单但够用的增强技巧
- 在时间部分加入机器标识:如取 IP 后两位或应用实例编号,变成
ORD20240520142305123-01-001 - 用 Base32/36 编码压缩长度:把 13 位数字转成 8~9 位字符,提升 URL 或二维码友好度
- 预留一位校验位(如 Luhn 算法):防止人工录入错误,适合线下扫码、手工单场景
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











