《 面试又翻车了》这次竟然和 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() 方法实现随机整数生成的。

相关文章
|
21天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
53 14
|
缓存 Java 数据库连接
「Java面试」五年Java程序员去某东面试竟然在MyBatis缓存这翻车
一个5年工作经验的小伙伴,去面某东被问到MyBatis何时使用一级缓存,何时使用二级缓存?去之前还特地复习了MyBatis的相关知识,想着自己用MyBatis用得比较熟练了,竟然在这道题上翻车了。 今天,我给大家来分享一下MyBatis的缓存机制。
87 0
|
Oracle Java 关系型数据库
《 面试又翻车了》这次竟然和 Random 有关?(下)
《 面试又翻车了》这次竟然和 Random 有关?(下)
148 1
《 面试又翻车了》这次竟然和 Random 有关?(下)
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
1月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
57 4
|
2月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
92 2
|
2月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
37 0
|
4月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
下一篇
DataWorks