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。
下一篇→ 并发编程的内存泄漏陷阱💣