
ThreadLocalRandom 比 Random 快,但不能直接替换
多线程下用 Random 生成随机数会竞争同一把锁(seed 更新的 CAS 或 synchronized),性能掉得明显;ThreadLocalRandom 每个线程持有一份独立实例,完全无锁。但它的设计不是为了“替代” Random,而是专用于多线程场景——它不支持设置种子(setSeed())、不能序列化、也不能通过 new ThreadLocalRandom() 实例化。
- 必须用静态工厂方法获取:
ThreadLocalRandom.current() - 只应在多线程环境(如 ForkJoinPool、Servlet 容器、CompletableFuture)中使用;单线程反复调用
current()反而比复用一个Random实例慢(有线程局部变量查找开销) - 若需可重现的随机序列(比如测试、游戏存档),只能用
Random,ThreadLocalRandom不提供setSeed()
并发环境下 new Random() 是错的
很多人以为“每个线程 new 一个 Random”就安全了,其实不对——Random 的默认构造函数会读取系统时间 + AtomicLong 做种子,高并发下大量线程几乎同时初始化,很可能得到相同种子,从而产出完全一样的随机数序列。
- 错误写法:
new Random()放在循环或线程入口里 - 正确做法:要么用
ThreadLocalRandom.current(),要么用带强熵种子的new Random(secureSeed)(比如从SecureRandom获取一次种子) - 注意
SecureRandom本身有性能开销,不适合高频调用,仅适合初始化种子
nextInt() / nextLong() 在 ThreadLocalRandom 中行为一致但不可预测
ThreadLocalRandom 的 nextInt(int bound)、nextLong(long bound) 和 Random 接口一致,都生成 [0, bound) 区间整数。但内部实现不同:它不依赖共享 seed,而是基于线程本地的 LCG(线性同余生成器)+ 一些位运算混洗,所以输出序列无法跨线程对齐或复现。
- 不要试图用
ThreadLocalRandom做需要确定性结果的逻辑(比如分布式任务分片时按随机数路由,又要求各节点结果一致) - 如果 bound 是 2 的幂,
ThreadLocalRandom会走快速路径(位与操作),比Random略快;否则仍需循环重试避免偏移,这点两者一样 - 边界值 bug 风险低,但要注意:当
bound 时两者都抛 <code>IllegalArgumentException,别漏判
Spring 或 Tomcat 环境下别在 Bean 里注入 Random 实例
Spring 默认单例 Bean,如果在类字段里声明 private final Random random = new Random();,这个实例会被所有请求线程共享,立刻退化为锁竞争场景。Tomcat 的 HttpServlet 实例也类似——不是每次请求新建对象。
立即学习“Java免费学习笔记(深入)”;
- 解决方案一:字段改为
ThreadLocalRandom.current()调用(推荐) - 解决方案二:用
@Scope("prototype")+ 构造注入,但增加 GC 压力且不易管控生命周期 - 切记:不要用
static Random,这是最常见也最隐蔽的并发陷阱之一
真正难处理的是那些既要随机性、又要可重现、还要跨服务一致的场景——这时候连 ThreadLocalRandom 和 Random 都不够用,得引入外部种子分发或哈希派生逻辑,而不是靠 JDK 内置随机器硬扛。










