← 返回题目列表

ThreadLocalRandom 是什么?为什么并发下比 Random 快?

中等 第 30 / 31 题 更新于 2026/07/27
ThreadLocalRandomRandom并发CAS

简化版

ThreadLocalRandom(Java 7 引入)是并发场景下生成随机数的工具,比 Random 在多线程下快得多。原因:Random 内部用一个「共享的种子(seed)」,每次生成随机数都要用 CAS 更新这个种子——多线程并发时大量线程竞争同一个种子的 CAS,失败重试激烈、性能差;ThreadLocalRandom每个线程有自己独立的种子(种子存在线程本地,类似 ThreadLocal 的思想),生成随机数时更新自己的种子、不和其他线程竞争,所以并发下快很多。用法也不同:不用 new,而是用静态方法 ThreadLocalRandom.current().nextInt()。一句话:Random 多线程下竞争共享种子(CAS 争抢)慢、ThreadLocalRandom 每线程独立种子无竞争快

详细版

Random 的并发瓶颈

Random random = new Random();   // 多个线程共享这一个 Random

// Random.next() 内部(简化):
protected int next(int bits) {
    long oldSeed, nextSeed;
    do {
        oldSeed = seed.get();              // 读共享种子
        nextSeed = (oldSeed * MUL + ADD) & MASK;   // 算新种子
    } while (!seed.compareAndSet(oldSeed, nextSeed));  // CAS 更新种子(竞争点!)
    return (int)(nextSeed >>> (48 - bits));
}
// 多线程并发调 nextInt → 都竞争 seed 的 CAS → 大量失败重试 → 慢

ThreadLocalRandom 的解法

// 用法:不 new,用 current() 拿当前线程的实例
int r = ThreadLocalRandom.current().nextInt(100);   // 0~99
long l = ThreadLocalRandom.current().nextLong();
double d = ThreadLocalRandom.current().nextDouble();

// 原理:每个线程有自己的种子(存在 Thread 对象的字段里)
//   生成随机数时更新"自己线程的种子",不和其他线程共享 → 无 CAS 竞争

Random vs ThreadLocalRandom

维度RandomThreadLocalRandom
种子共享一个(多线程竞争)每线程独立
并发性能差(CAS 争抢种子)(无竞争)
用法new Random()ThreadLocalRandom.current()
单线程够用也可用(但没必要)

⚠️ ThreadLocalRandom 不能用 new 创建,也不能把 current() 的返回值缓存到多个线程共享——它设计成「每个线程调 ThreadLocalRandom.current() 拿到属于自己线程的实例」。如果你把一个线程的 current() 结果保存起来给别的线程用,就破坏了「每线程独立」的前提(多个线程用同一个实例又会竞争)。正确用法永远是「在需要的地方 ThreadLocalRandom.current().nextXxx()」(current() 很轻量,每次调开销很小)。

完整版教学

一、Random 的并发问题:共享种子的竞争

要理解 ThreadLocalRandom,先看 Random 在并发下的问题——它有一个「共享的种子」,多线程竞争这个种子

Random 生成随机数的原理:
  Random 内部维护一个"种子(seed)"
  每次生成随机数:用当前种子算出随机数 + 算出下一个种子(更新种子)
  → 随机数序列由种子的连续变化决定

并发问题:
  多个线程共享一个 Random 实例 → 共享一个种子
  每次生成随机数都要"更新种子"(用 CAS 保证线程安全)
  → 多线程并发时,大量线程同时 CAS 更新同一个种子
  → CAS 竞争激烈、失败重试多、CPU 空转 → 性能差

RandomCAS(AtomicLong 的种子) 保证线程安全——但这带来了「所有线程竞争同一个种子的 CAS」的问题(前面 CAS 题讲过:高并发下 CAS 争抢同一个变量会大量失败重试)。种子是一个「热点」,多线程都要更新它,竞争激烈。所以 Random 虽然线程安全,但在高并发下性能很差(大量线程卡在 CAS 重试上)。理解「Random 用共享种子+CAS 保证线程安全、但高并发下竞争种子导致性能差」,就理解了 ThreadLocalRandom 要解决的问题——消除种子的竞争。

二、ThreadLocalRandom 的解法:每线程独立种子

ThreadLocalRandom 的解法是「让每个线程有自己独立的种子,不再共享」——这样就没有竞争了:

ThreadLocalRandom 的核心思想:
  不再让所有线程共享一个种子
  而是"每个线程有自己的种子"(存在 Thread 对象的字段里)
  → 每个线程更新"自己的种子"生成随机数
  → 不同线程各更新各的、互不干扰 → 无 CAS 竞争

类比 ThreadLocal:
  ThreadLocal 让每个线程有自己的变量副本(避免共享变量的竞争)
  ThreadLocalRandom 让每个线程有自己的种子(避免共享种子的竞争)
  → 都是"用空间换并发、消除竞争"的思路

核心思路是「把共享变成独立」——Random 的问题是「共享种子导致竞争」,ThreadLocalRandom 让每个线程有自己的种子(存在 Thread 对象里,通过 current() 访问当前线程的种子),各更新各的、无竞争。这和 ThreadLocal(每线程独立变量)、LongAdder(分散 Cell)是同一个「用空间换并发、把热点分散」的思路。所以 ThreadLocalRandom 在高并发下性能远超 Random(无 CAS 竞争)。理解「ThreadLocalRandom 让每线程独立种子、消除共享种子的竞争、和 ThreadLocal 同思路」,就掌握了它高性能的原理——关键是「无竞争」。

三、用法:current() 而非 new

ThreadLocalRandom 的用法和 Random 不同——不用 new,而是用静态方法 current() 获取当前线程的实例

// ✗ 不能这样(构造器是私有的)
// ThreadLocalRandom r = new ThreadLocalRandom();

// ✓ 正确:用 current() 获取当前线程的 ThreadLocalRandom
int r1 = ThreadLocalRandom.current().nextInt();           // 任意 int
int r2 = ThreadLocalRandom.current().nextInt(100);        // 0~99
int r3 = ThreadLocalRandom.current().nextInt(10, 20);     // 10~19(指定范围)
long l = ThreadLocalRandom.current().nextLong(1000);
double d = ThreadLocalRandom.current().nextDouble();

ThreadLocalRandom.current() 的作用是「获取『属于当前线程』的 ThreadLocalRandom 实例」——它返回一个全局单例(ThreadLocalRandom 本身是单例),但内部操作的是「当前线程的种子」(种子存在 Thread 对象里)。所以每次调 current().nextXxx() 都是「用当前线程自己的种子」生成随机数、无竞争。关键:每次要随机数都 current().nextXxx()current() 很轻量),别缓存 current() 的结果跨线程用。它还比 Random 多了「指定范围」的方法(nextInt(origin, bound))。理解「ThreadLocalRandom 用 current() 获取当前线程实例、每次要随机数就 current().nextXxx()、别跨线程缓存」,就掌握了它的正确用法。

四、为什么并发下快:无竞争

把 Random 和 ThreadLocalRandom 的并发性能差异讲清楚:

高并发下(如 100 个线程同时生成随机数):

Random(共享种子):
  100 个线程都要 CAS 更新同一个种子
  → 只有一个能成功、其他 99 个失败重试
  → 大量 CAS 失败 + 重试 + CPU 空转 → 性能急剧下降
  → 线程越多,竞争越激烈,越慢

ThreadLocalRandom(独立种子):
  100 个线程各更新各自的种子(各在各的 Thread 对象里)
  → 完全无竞争、无 CAS 失败重试
  → 每个线程独立生成、性能不受线程数影响 → 快很多

性能差距:高并发下 ThreadLocalRandom 可能比 Random 快几倍到几十倍

核心:Random 的性能瓶颈是「所有线程争抢一个种子的 CAS」(线程越多越慢),ThreadLocalRandom 通过「每线程独立种子」消除了这个竞争(性能不受线程数影响)。这个差异在高并发下非常显著(可能几倍到几十倍)。所以「并发场景生成随机数,用 ThreadLocalRandom 而非 Random(或共享的 Random)」是明确的最佳实践。理解「Random 高并发下竞争种子越来越慢、ThreadLocalRandom 无竞争性能稳定、并发下快几倍到几十倍」,就理解了「为什么并发用 ThreadLocalRandom」——就是「无竞争」的性能优势。

五、和 Math.random() 的关系

顺带理清 Math.random() 和 Random/ThreadLocalRandom 的关系:

Math.random():
  返回 [0, 1) 的 double 随机数
  底层其实是用一个"共享的 Random 实例"(静态的)
  → 所以 Math.random() 在高并发下也有 Random 的竞争问题(共享种子)!

对比:
  Math.random():简单,但底层是共享 Random,高并发下性能差
  new Random():每次 new 一个,但多线程共享同一个实例仍竞争
  ThreadLocalRandom:每线程独立种子,高并发下最优

所以高并发生成随机数:别用 Math.random()、别用共享的 Random → 用 ThreadLocalRandom

Math.random() 是最简单的随机数写法,但它底层用一个共享的静态 Random 实例——所以在高并发下和 Random 有一样的竞争问题(共享种子的 CAS 争抢)。所以:高并发场景别用 Math.random()(底层共享 Random 慢)、别用多线程共享的 Random,用 ThreadLocalRandom.current()。单线程或低并发下 Math.random()/Random 都够用,但高并发就要 ThreadLocalRandom。理解「Math.random 底层是共享 Random 高并发下也慢、高并发用 ThreadLocalRandom」,就补齐了随机数生成的完整认知——知道各方案的并发表现。

六、注意点与选型

ThreadLocalRandom 的注意点和选型:

注意点:
  ① 不能 new(构造器私有),用 ThreadLocalRandom.current()
  ② 别把 current() 的结果缓存跨线程用(破坏"每线程独立"、又变成竞争)
  ③ 每次要随机数就 current().nextXxx()(current() 很轻量、开销小)
  ④ 不是密码学安全的随机数(要安全随机用 SecureRandom)

选型:
  单线程/低并发 → Random / Math.random() 都行(简单)
  高并发生成随机数 → ThreadLocalRandom(无竞争、快)
  安全敏感(token、密钥、盐值)→ SecureRandom(密码学安全,但慢)

选型准则:① 单线程/低并发用 Random 或 Math.random(简单够用);② 高并发用 ThreadLocalRandom(无竞争、性能好);③ 安全敏感(生成 token、密钥、盐值)用 SecureRandom(密码学安全的随机数,不可预测,但性能慢)。注意 ThreadLocalRandom 不是密码学安全的(它是「快的伪随机数」,不适合安全场景)——安全场景要用 SecureRandom(虽然慢,但随机性强、不可预测)。理解「单线程用 Random、高并发用 ThreadLocalRandom、安全用 SecureRandom」,就掌握了随机数方案的完整选型——按「并发 vs 安全」的需求选。

记忆钩子:「ThreadLocalRandom(Java 7)并发下比 Random 快很多:Random 用共享种子、多线程都 CAS 更新它(竞争激烈失败重试多、越多线程越慢),ThreadLocalRandom 让每线程独立种子(和 ThreadLocal 同思路、无竞争);用法用 ThreadLocalRandom.current().nextXxx()(不 new、别跨线程缓存);Math.random() 底层也是共享 Random 高并发慢;选型:单线程 Random、高并发 ThreadLocalRandom、安全用 SecureRandom(密码学安全但慢)」

七、常见误区与追问

  • 误区:ThreadLocalRandom 和 Random 性能一样。 高并发下 ThreadLocalRandom 快很多——Random 多线程竞争共享种子的 CAS(越多线程越慢),ThreadLocalRandom 每线程独立种子无竞争(性能不受线程数影响)。
  • 误区:ThreadLocalRandom 可以 new。 不能——构造器私有,用 ThreadLocalRandom.current() 获取当前线程的实例;且别把 current() 的结果缓存跨线程用(会破坏每线程独立、又变竞争)。
  • 误区:Math.random() 在高并发下没问题。 有问题——它底层用一个共享的静态 Random 实例,高并发下同样竞争共享种子;高并发生成随机数应用 ThreadLocalRandom。
  • 误区:ThreadLocalRandom 可以用于生成 token/密钥。 不行——它是「快的伪随机数」、不是密码学安全的(可能被预测);安全敏感场景(token、密钥、盐值)用 SecureRandom(密码学安全但慢)。
  • 追问:ThreadLocalRandom 为什么比 Random 并发下快? Random 用一个共享种子、每次生成随机数都要 CAS 更新它,多线程并发时竞争激烈(失败重试多、CPU 空转);ThreadLocalRandom 让每个线程有自己独立的种子(存在 Thread 对象里),各更新各的、无 CAS 竞争,所以快很多。
  • 追问:ThreadLocalRandom 怎么用?ThreadLocalRandom.current().nextXxx()(不能 new,current() 获取当前线程的实例、很轻量),每次要随机数就调,别把 current() 结果缓存跨线程共享。
  • 追问:随机数方案怎么选? 单线程/低并发用 Random 或 Math.random(简单);高并发用 ThreadLocalRandom(无竞争、快);安全敏感(token/密钥/盐值)用 SecureRandom(密码学安全、不可预测,但慢)。

八、加强记忆

ThreadLocalRandom(Java 7)是并发场景生成随机数的工具,比 Random 在多线程下快很多。原因:Random 用一个「共享的种子」,每次生成随机数都要 CAS 更新这个共享种子——多线程并发时大量线程竞争同一个种子的 CAS(失败重试激烈、CPU 空转、越多线程越慢);ThreadLocalRandom 让每个线程有自己独立的种子(存在 Thread 对象里,和 ThreadLocal「每线程独立变量」同思路),各更新各的、无 CAS 竞争(性能不受线程数影响),所以并发下快几倍到几十倍。用法:不能 new(构造器私有),用 ThreadLocalRandom.current().nextXxx()(获取当前线程的实例、很轻量,每次要随机数就调、别把 current() 结果跨线程缓存)。注意 Math.random() 底层也是共享 Random(高并发下同样慢)。选型单线程/低并发用 Random 或 Math.random(简单)、高并发用 ThreadLocalRandom(无竞争)、安全敏感(token/密钥/盐值)用 SecureRandom(密码学安全但慢,ThreadLocalRandom 不是密码学安全的)。一句话「Random 共享种子多线程 CAS 争抢慢、ThreadLocalRandom 每线程独立种子无竞争快(同 ThreadLocal 思路),用 current().nextXxx() 别 new 别跨线程缓存,高并发用它、安全用 SecureRandom」。