Random类及其局限性
在JDK7之前包括现在Random都是使用比较广泛的随机数生成工具。在java.lang.Math中随机数生成也是使用的java.util.Random的实例。
下面是Random的一种常见的使用方式。
基本步骤就是生成一个Random实例,然后通过这个实例的方法去生成随机数字。
1 | public class RandomTest { |
随机数的生成需要一个默认的种子,这个种子是一个long类型的数字。我们可以查看一下Random的源码。
1 | // 无参构造函数其实是通过当前时间生成long类型的种子的 |
我们来看一下nextInt()方法的源码
1 | // 无参nextInt方法调用了next方法 |
看了以上的代码,我们先不管种子是否是原子变量,如果多个线程去调用这个随机方法获得种子然后生成随机数,因为方法里新种子的生成依赖于旧种子,而旧种子是存放在共享变量里的,这里就会导致线程不安全问题,如果线程1生成一个种子,然后线程2和线程3同时调用该方法然后生成新种子,这个时候两个线程调用的旧种子是一样的,又因为旧种子变成新种子的算法是固定的,所以这两个线程得到的是同一个新种子,那么就意味着他们会生成同样的随机数。
而我们要注意的是在next方法里seed是被声明成AtomicLong类型的,它是原子变量,所以这样就可以解决线程安全的问题了(同一时刻只有一个线程能对这个原子变量进行操作),后面原子变量的更新操作使用的是CAS操作,同一时刻只有一个线程能更新成功,这样就会 导致大量线程自旋重试 ,这样就极大地降低了并发性能。
ThreadLocalRandom
ThreadLocalRandom很好的解决了Random在高并发场景下的缺陷和不足。与ThreadLocal的原理一样ThreadLocalRandom使用的也是 线程封闭技术 。
使用方式和Random差不多。
1 | public class RandomTest { |
因为在Random中种子是共享变量,所以在多线程环境下会出现线程安全问题。而ThreadLocalRandom则是把种子变为线程本地变量。这样每个线程就会通过自己线程里的旧种子去更新种子。
ThreadLocalRandom源码分析

我们可以发现ThreadLocalRandom是继承了Random类的,但是需要注意的是ThreadLocalRandom并没有使用Random的seed变量,具体的变量存放在Thread中的ThreadLocalRandomSeed中(存放在线程中)。当调用ThreadLocalRandom的nextInt方法的时候,会获取当前线程的ThreadLocalRandomSeed变量并通过这个种子更新种子然后使用新种子来随机生成数字。
其中ThreadLocalRandom中的seeder和probeGenerator是两个原子性变量,在初始化调用线程的种子和探针的时候会用到他们,每个线程只会调用一次。
另外instance是ThreadLocalRandom的一个实例而且是static的,也就是说多个线程获取的是同一个实例,但是因为种子是存放在线程中的,所以不会产生安全问题。
Unsafe机制
1 | private static final sun.misc.Unsafe UNSAFE; |
ThreadLocalRandom.current()方法
1 | public static ThreadLocalRandom current() { |
int nextInt(int bound)方法
1 | public int nextInt(int bound) { |
总结
因为Random的种子生成随机数的方法,在Random中的种子是共享的所以多线程会出现并发问题,而Random中将种子声明成原子变量并且使用CAS更新会导致在多线程环境下多个线程去竞争资源,从而导致大量线程自旋,浪费资源和降低并发能力。
在ThreadLocalRandom中使用了线程封闭技术来解决这个问题,线程封闭即使线程本地化,将共享变量进行本地化,从而避免了线程安全问题和提高了并发能力。