第三章节:ThreadLocalRandom类是JDK 7 在JUC包下新增的随机数生成器,它弥补了Random类在多线程下的缺陷。这一张会学习到为何增加该类以及它的实现原理。
————————————————————————————————————————————————————
3.1 Random类及其局限性
java.util.Random类是应用比较广泛的随机数生成类,而且在java.lang.Math中的随机数生成也是使用的该Random实例。其使用方法如下:
(1)创建一个默认种子的随机数生成器:随机数生成器需要一个种子,不指定则使用默认种子,可以使用构造函数指定。种子实际上是一个long类型的数字。
(2)在指定范围内输出10个随机数:通过传参指定随机数范围。
上边获取随机数的方法可以分为两步:
1、根据老的种子生成新的种子,并用新种子将老种子替换掉。
2、根据新种子计算随机数。
(4)(5)两步可以抽象成两个固定的函数f(seed)和g(seed,bound)。
那么在单线程下,每次生成的新种子必定不同,因此根据新种子计算的随机数也不同,这没有问题。
但是在多线程下,由于(4)(5)两步的算法是固定的,所以可能同时有多个线程根据同一个老种子去生成新种子,那么最终根据新种子去计算出的随机数将是相同的。这不是我们想要的结果。要想避免这个问题,必须保证新种子的原子性,即当一个线程利用老种子生成了新种子,必须用新种子替换老种子的同时,还要使其他线程利用更新后的老种子重新生成新种子。
其实在(4)步next()方法中,利用原子变量避免了这个问题。种子变量被声明为原子变量类型,它只可以被一个线程更新。当多个线程利用同一个老种子计算出新种子后,保证了只有一个线程能够更新老种子。而其他线程会因为更新老种子失败而重新获取老种子计算新种子。
❀总结:java.util.Random工具类利用原子变量保证了多线程下随机数生成不会重复,但是由于这个机制会造成其他线程多次重新获取老种子,降低并发性能。针对这一问题,就要看ThreadLocalRandom类了。
3.2 ThreadLocalRandom类
为了弥补多线程高并发下Random类的缺陷,在JUC包下新增了ThreadLocalRandom类。
package com.learnThread.demo.part3;
import java.util.concurrent.ThreadLocalRandom;
/**
* @Author: tongys
* @Date: 2020/1/6
*/
public class TestThreadLocalRandom {
public static void main(String[] args) {
//获取一个随机数生成器
ThreadLocalRandom random = ThreadLocalRandom.current();
//输出10个范围在0(包括)~5(不包括)之间的随机数
for (int i =0;i<10;i++){
System.out.println(random.nextInt(5));
}
}
}
Random类在多线程下的问题是由于多个线程操作同一个原子变量类型的种子导致的,ThreadLocalRandom下每个线程都有自己的种子,自己操作自己的种子就不会产生该问题了。
Random类:
ThreadLocalRandom类:
3.3 原理分析
看以下类图:
ThreadLocalRandom类继承了Random类并重写了nextInt()方法。threadLocalRandomSeed是线程级别的种子变量,每个线程都有自己的种子变量,在线程创建ThreadLocalRandom随机数生成器时会创建该种子变量。这个种子变量只是一般的long类型变量,不是原子类型的。因为每个线程都有自己的种子变量,所以不需要是原子类型了。
其中seeder和probeGenerator是原子型变量,在初始化调用线程的种子和探针变量时会用到它们,每个线程只使用一次。
其中instance是ThreadLocalRandom的一个实例,该变量是static静态的。当线程通过current()获取ThreadLocalRandom的实例时其实获取的是同一个。由于种子是保存在各自的线程中,ThreadLocalRandom的实例中只保存了与线程无关的通用算法,所以是线程安全的。
最后看一下一个线程下的种子变量是如何更新的:
使用r = UNSAFE.getLong(t,SEED) 获得当线程下的threadLocalRandomSeed种子值,然后增加GAMMA后获得新种子值,最后调用UNSAFE.putLong更新旧种子的值。
3.4 总结
这一张主要讲了java.util.Random工具类的实现原理和多线程下的缺点,以及能够在多线程下避免该缺点的ThreadLocalRandom类。ThreadLocalRandom以Random类为基础,继承的同时进行了改动,每个初始化ThreadLocalRandom随机数生成器的线程都能操作和维护自己独有的threadLocalRandomSeed种子,从而解决了使用Random类时因多个线程同时操作同一个种子导致的问题。