导读:本期聚焦于小伙伴创作的《Java里Random和ThreadLocalRandom到底怎么用才正确?》,敬请观看详情。高并发服务里用Random生成流水号偶尔出现重复,根源往往是多线程争用同一个随机数发生器。Random基于原子性种子更新,竞争剧烈时性能陡降。JDK 7引入的ThreadLocalRandom为每个线程维护独立种子,彻底规避锁竞争。本文厘清两者底层种子的更新机制,给出单线程与多线程场景下的正确写法,并提醒不要用多线程共享Random实例做唯一性凭证。理解这些差异,才能写出既安全又高效的Java随机数代码。

在Java中生成随机数最常见的两个类是java.util.Randomjava.util.concurrent.ThreadLocalRandom。许多人在写工具类时随手new一个Random丢进全局变量,多线程一跑就会发现CPU占用偏高,甚至偶发数值碰撞。其实这两者的设计目标完全不同:Random偏向通用、单线程友好;ThreadLocalRandom则是为高并发场景专门优化的线程本地随机数实现。下面我们通过原理、用法和性能三个层面把它们的正确打开方式讲清楚。

Java里Random和ThreadLocalRandom到底怎么用才正确?

Random的底层原理与基础用法

Random内部维护一个48位的原子种子(seed),每次调用nextInt()nextDouble()等方法时,都会通过线性同余算法更新种子,再根据新种子计算出伪随机值。为了保证多线程安全,Random使用AtomicLong来保存种子,并通过CAS(比较并交换)方式更新。这意味着在多线程同时调用同一个Random实例时,只有一个线程能成功更新种子,其余线程会自旋重试,从而引发竞争。

在单线程或者低并发环境中,这种开销可以忽略不计。基础用法非常简单,只需要创建实例并调用对应方法即可。下面的代码展示了如何用Random生成一个0到99之间的整数,以及一段连续的随机布尔值:

import java.util.Random;

public class RandomDemo {
    public static void main(String[] args) {
        // 使用系统时间作为种子创建Random
        Random random = new Random();
        // 生成[0, 100)之间的整数
        int value = random.nextInt(100);
        System.out.println("随机整数: " + value);
        // 生成随机布尔值
        boolean flag = random.nextBoolean();
        System.out.println("随机布尔: " + flag);
        // 生成随机浮点数,范围[0.0, 1.0)
        double d = random.nextDouble();
        System.out.println("随机浮点: " + d);
    }
}

值得注意的是,如果你在循环中频繁创建Random实例,比如for (int i=0;i<1000;i++) new Random().nextInt(),由于每次构造都要关联系统时钟,不仅浪费内存,还可能在极短时间内产生相同种子,导致随机数序列重复。因此推荐将Random实例复用,或者在明确需要可复现结果时用固定种子,例如new Random(12345)

Random还有一个容易被忽视的问题:它并不是加密安全的。如果业务涉及令牌、密钥、验证码等安全敏感场景,应该使用java.security.SecureRandom,而不是Random。Random的算法是公开的线性同余,知道部分输出就能推算后续序列,绝不能用于安全用途。

ThreadLocalRandom如何解决并发争用

从JDK 7开始,ThreadLocalRandom被引入到java.util.concurrent包中。它的核心思路是为每个线程分配独立的随机数生成状态,这些状态并不保存在共享对象里,而是直接存放在线程自身的字段中(通过Unsafe机制注入)。这样一来,线程在生成随机数时完全不需要访问共享变量,也就不存在锁或CAS竞争。

使用ThreadLocalRandom时,不能像Random那样直接new对象,而是要通过静态方法ThreadLocalRandom.current()获取当前线程对应的实例。之后调用方式与Random类似,但性能在多线程下有明显优势。下面是一段多线程测试示例,展示每个线程独立获取随机数的写法:

import java.util.concurrent.ThreadLocalRandom;

public class ThreadLocalRandomDemo {
    public static void main(String[] args) {
        Runnable task = () -> {
            // 获取当前线程的ThreadLocalRandom实例
            ThreadLocalRandom random = ThreadLocalRandom.current();
            int num = random.nextInt(1, 101); // 生成[1, 101)即1到100
            System.out.println(Thread.currentThread().getName() + " 得到: " + num);
        };
        // 启动多个线程验证无共享争用
        for (int i = 0; i < 5; i++) {
            new Thread(task).start();
        }
    }
}

ThreadLocalRandom不仅避免了竞争,还针对常见范围做了优化,例如nextInt(int origin, int bound)可以直接指定区间,而不需要像Random那样自己做取模运算。不过要注意,ThreadLocalRandom的实例不要跨线程传递使用,因为它绑定的是“调用current()那一刻”的线程,若把某个线程拿到的引用交给另一个线程用,会失去隔离性甚至拿到错误数据。

另外,ThreadLocalRandom同样不是加密安全的,它只是解决了性能与线程安全问题,并不提供不可预测性保障。如果在Web服务器中为每个请求生成追踪ID,用它可以大幅降低延迟;但若生成密码重置令牌,仍需SecureRandom。

性能对比与选型建议

我们可以通过简单的基准思路理解两者差异:假设有16个线程,每个线程循环一千万次调用nextInt()。使用共享Random时,由于AtomicLong的CAS失败重试,总耗时可能是ThreadLocalRandom的数倍甚至十倍以上;而ThreadLocalRandom因为零共享,基本随线程数线性扩展。虽然在单线程下两者差距很小,但现代Java服务几乎必然运行在线程池之上,因此并发环境默认选ThreadLocalRandom是更稳妥的做法。

选型上可以遵循以下原则:如果是小型单机工具、单元测试、或者明确单线程逻辑,用Random并复用实例即可;如果是Web应用、异步任务、并行流(parallel stream)里需要随机数,请统一用ThreadLocalRandom.current()。切忌将Random实例作为静态成员变量在多线程中共享,也切忌在并行流里用外部Random,因为并行流底层就是多线程拆分。下面的代码展示了并行流中的正确写法:

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ParallelRandomDemo {
    public static void main(String[] args) {
        // 并行流中每个线程自行获取ThreadLocalRandom
        List<Integer> list = IntStream.range(0, 100)
                .parallel()
                .mapToObj(i -> ThreadLocalRandom.current().nextInt(1000))
                .collect(Collectors.toList());
        System.out.println("生成数量: " + list.size());
    }
}

最后提醒一点,无论用哪个类,随机数都只是“伪随机”,不能当作唯一ID的唯一来源。如果业务要求严格不重复,应该结合数据库自增、雪花算法或UUID,随机数最多只作为混淆因子。理清Random与ThreadLocalRandom的职责边界,才能既写出高性能代码,又避开并发陷阱。

RandomThreadLocalRandomJava随机数修改时间:2026-08-14 11:51:31

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。