ThreadLocalRandom:并发场景的随机数生成器🎲

31 阅读2分钟

Random在多线程下是性能杀手,ThreadLocalRandom让每个线程有自己的种子,性能提升10倍!

一、Random的性能问题

问题代码

Random random = new Random();

// 多线程并发获取随机数
for (int i = 0; i < 100; i++) {
    new Thread(() -> {
        for (int j = 0; j < 10000; j++) {
            int num = random.nextInt(); // CAS竞争!
        }
    }).start();
}

Random的内部实现

public class Random {
    private final AtomicLong seed; // 所有线程共享!
    
    public int nextInt() {
        long oldSeed, nextSeed;
        do {
            oldSeed = seed.get();
            nextSeed = (oldSeed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldSeed, nextSeed)); // CAS竞争
        
        return (int)(nextSeed >>> 16);
    }
}

问题: 所有线程竞争同一个seed,高并发下性能差!

二、ThreadLocalRandom的解决方案

正确用法

// ❌ 错误:不要new
ThreadLocalRandom random = new ThreadLocalRandom(); // 不要这样!

// ✅ 正确:用current()
int num = ThreadLocalRandom.current().nextInt();
double d = ThreadLocalRandom.current().nextDouble();

原理

public class ThreadLocalRandom extends Random {
    
    // 每个线程有自己的seed(存在Thread对象中)
    public static ThreadLocalRandom current() {
        return Thread.currentThread().threadLocalRandomSeed;
    }
    
    public int nextInt() {
        // 直接用当前线程的seed,无CAS竞争!
        return mix32(nextSeed());
    }
}

关键: 每个线程独立的seed,无竞争!

三、性能对比

public class PerformanceTest {
    private static final int THREADS = 100;
    private static final int ITERATIONS = 100_000;
    
    public static void main(String[] args) throws InterruptedException {
        testRandom();
        testThreadLocalRandom();
    }
    
    private static void testRandom() throws InterruptedException {
        Random random = new Random();
        long start = System.currentTimeMillis();
        
        Thread[] threads = new Thread[THREADS];
        for (int i = 0; i < THREADS; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < ITERATIONS; j++) {
                    random.nextInt();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) t.join();
        
        long time = System.currentTimeMillis() - start;
        System.out.println("Random耗时: " + time + "ms");
    }
    
    private static void testThreadLocalRandom() throws InterruptedException {
        long start = System.currentTimeMillis();
        
        Thread[] threads = new Thread[THREADS];
        for (int i = 0; i < THREADS; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < ITERATIONS; j++) {
                    ThreadLocalRandom.current().nextInt();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) t.join();
        
        long time = System.currentTimeMillis() - start;
        System.out.println("ThreadLocalRandom耗时: " + time + "ms");
    }
}

结果:

Random耗时: 5200ms
ThreadLocalRandom耗时: 450ms  ← 快10倍!

四、使用场景

✅ 适合场景

// 1. 并发随机数生成
IntStream.range(0, 1000000).parallel().forEach(i -> {
    int random = ThreadLocalRandom.current().nextInt(100);
});

// 2. 随机ID生成
public String generateId() {
    return UUID.randomUUID().toString() + 
           ThreadLocalRandom.current().nextInt(10000);
}

// 3. 随机延迟
public void randomDelay() throws InterruptedException {
    int delay = ThreadLocalRandom.current().nextInt(100, 500);
    Thread.sleep(delay);
}

// 4. 负载均衡随机选择
public Server randomServer(List<Server> servers) {
    int index = ThreadLocalRandom.current().nextInt(servers.size());
    return servers.get(index);
}

❌ 不适合场景

// 1. 单线程
public void singleThread() {
    for (int i = 0; i < 1000; i++) {
        // 单线程用Random更简单
        int num = new Random().nextInt();
    }
}

// 2. 需要固定seed(可重现)
Random random = new Random(12345); // 固定种子
// ThreadLocalRandom无法指定种子

五、API一览

ThreadLocalRandom random = ThreadLocalRandom.current();

// 整数
random.nextInt();              // [0, Integer.MAX_VALUE)
random.nextInt(100);           // [0, 100)
random.nextInt(10, 100);       // [10, 100)

// 长整数
random.nextLong();
random.nextLong(100L);
random.nextLong(10L, 100L);

// 浮点数
random.nextDouble();           // [0.0, 1.0)
random.nextDouble(10.0);       // [0.0, 10.0)
random.nextDouble(1.0, 10.0);  // [1.0, 10.0)

// 布尔
random.nextBoolean();

// 高斯分布
random.nextGaussian();

六、面试高频问答💯

Q: ThreadLocalRandom比Random快的原因?

A:

  • Random:所有线程共享seed,CAS竞争激烈
  • ThreadLocalRandom:每个线程独立seed,无竞争

Q: 为什么用current()而不是new?

A: current()获取当前线程的实例,new会创建新对象但无法关联线程。

Q: ThreadLocalRandom能设置种子吗?

A: 不能,种子由系统自动生成。如需固定种子,用Random。


下一篇→ 并发编程的内存泄漏陷阱💣