《 面试又翻车了》这次竟然和 Random 有关?(上)

简介: 《 面试又翻车了》这次竟然和 Random 有关?(上)

小强最近面试又翻车了,然而令他郁闷的是,这次竟然是栽到了自己经常在用的 Random 上......


面试问题


既然已经有了 Random 为什么还需要 ThreadLocalRandom?


正文


Random 是使用最广泛的随机数生成工具了,即使连 Math.random() 的底层也是用 Random 实现的Math.random() 源码如下:


微信图片_20220117200047.png


可以看出 Math.random() 直接指向了 Random.nextDouble() 方法。


Random 使用


这开始之前,我们先来了解一下 Random 的使用。


Random random = new Random();
for (int i = 0; i < 3; i++) {
    // 生成 0-9 的随机整数
    random.nextInt(10);
}


以上程序的执行结果为:


1

0

7


Random 源码解析


可以看出 Random 是通过 nextInt() 方法生成随机整数的,那他的底层的是如何实现的呢?我们来看他的实现源码:


/**
 * 源码版本:JDK 11
 */
public int nextInt(int bound) {
    // 验证边界的合法性
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
  // 根据老种子生成新种子
    int r = next(31);
    // 计算最大值
    int m = bound - 1;
    // 根据新种子计算随机数
    if ((bound & m) == 0)  // i.e., bound is a power of 2
        r = (int)((bound * (long)r) >> 31);
    else {
        for (int u = r;
                u - (r = u % bound) + m < 0;
                u = next(31))
            ;
    }
    return r;
}


从以上源码我们可以看出,整个源码最核心的部分有两块:


  1. 根据老种子生成新种子;
  2. 根据新种子计算出随机数。


根据新种子计算出随机数的代码已经很明确了,我们需要确认一下 next() 方法是如何实现的,继续看源码:


/**
 * 源码版本:JDK 11
 */
protected int next(int bits) {
    // 声明老种子和新种子
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        // 获取原子变量种子的值
        oldseed = seed.get();
        // 根据当前种子计算出新种子的值
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed)); // 使用 CAS 更新种子
    return (int)(nextseed >>> (48 - bits));
}


根据以上源码可以看出,在使用老种子去获取新种子的时候,如果是多线程操作,则同一时刻只会有一个线程 CAS (Conmpare And Swap,比较并交换) 成功,其他失败的线程会通过自旋等待获取新种子,因此会有一定的性能消耗


当多线程使用同一个老种子来 CAS 的时候,只能有一个线程能够成功,而其他失败的线程只能通过自旋等待,这也是为什么 JDK 1.7 会引入 ThreadLocalRandom 的答案了,它主要为了提升多线程情况下 Random 的执行效率。


ThreadLocalRandom 使用


我们先来看 ThreadLocalRandom 的类关系图:


微信图片_20220117200049.png


可以看出 ThreadLocalRandom 继承于 Random 类,先来看它的使用:


ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
for (int i = 0; i < 3; i++) {
    // 生成 0-9 的随机数
    System.out.println(threadLocalRandom.nextInt(10));
}


以上程序的执行结果为:


1

7

5


可以看出 ThreadLocalRandom 和 Random 一样,都是通过 nextInt() 方法实现随机整数生成的。

相关文章
|
缓存 Java 数据库连接
「Java面试」五年Java程序员去某东面试竟然在MyBatis缓存这翻车
一个5年工作经验的小伙伴,去面某东被问到MyBatis何时使用一级缓存,何时使用二级缓存?去之前还特地复习了MyBatis的相关知识,想着自己用MyBatis用得比较熟练了,竟然在这道题上翻车了。 今天,我给大家来分享一下MyBatis的缓存机制。
78 0
|
Oracle Java 关系型数据库
《 面试又翻车了》这次竟然和 Random 有关?(下)
《 面试又翻车了》这次竟然和 Random 有关?(下)
136 1
《 面试又翻车了》这次竟然和 Random 有关?(下)
|
2月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
2月前
|
Java
【Java基础面试四】、介绍一下Java的数据类型
这篇文章介绍了Java的数据类型,包括8种基本数据类型(整数、浮点、字符、布尔)和3类引用数据类型(数组、类、接口),并提供了基本数据类型所占内存空间和数据范围的详细信息。
|
2月前
|
Java C++
【Java基础面试十七】、Java为什么是单继承,为什么不能多继承?
这篇文章讨论了Java单继承的设计原因,指出Java不支持多继承主要是为了避免方法名冲突等混淆问题,尽管Java类不能直接继承多个父类,但可以通过接口和继承链实现类似多继承的效果。
【Java基础面试十七】、Java为什么是单继承,为什么不能多继承?
|
2月前
|
Java
【Java基础面试三】、说一说你对Java访问权限的了解
这篇文章介绍了Java中的四种访问权限:private、default(无修饰符时的访问权限)、protected和public,以及它们分别在修饰成员变量/方法和类时的不同访问级别和规则。
【Java基础面试三】、说一说你对Java访问权限的了解
|
2月前
|
Java
【Java基础面试二】、个Java文件里可以有多个类吗(不含内部类)?
这篇文章讨论了Java文件中类的定义规则,指出一个Java文件可以包含多个类(不包含内部类),但其中最多只能有一个public类,且如果有public类,它的名称必须与文件名一致。
|
2月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
2月前
|
Java
【Java基础面试三十七】、说一说Java的异常机制
这篇文章介绍了Java异常机制的三个主要方面:异常处理(使用try、catch、finally语句)、抛出异常(使用throw和throws关键字)、以及异常跟踪栈(异常传播和程序终止时的栈信息输出)。
|
2月前
|
Java
【Java基础面试三十八】、请介绍Java的异常接口
这篇文章介绍了Java的异常体系结构,主要讲述了Throwable作为异常的顶层父类,以及其子类Error和Exception的区别和处理方式。