为了账号安全,请及时绑定邮箱和手机立即绑定

Java并发包中ThreadLocalRandom类

标签:
Java 源码

https://img1.sycdn.imooc.com//5e5a81140001291610801440.jpg

ThreadLocalRandom类是JDK7在JUC包下新增的随机数生成器,它弥补了Random类在多线程下的缺陷。

一、Random类及其局限性

java.util.Random常用来生成随机数,随机数的生成需要一个默认的种子,这个种子其实是一个long类型的数字,通常在创建Random对象时,通过构造函数指定,如果不指定的则会在默认构造函数内部生成一个默认的值。     

生成随机数通常需要以下两个步骤:   

1. 首先根据老的种子生成新的种子。

2. 然后根据新的种子来计算新的随机数。

比如Random的nextInt方法,源码如下:

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;
    }

在单线程环境下:每次调用nextInt()方法都是根据老的种子来计算新的种子,并生成随机数,可以保证随机数产生的随机性的。


在多线程环境下:多个线程可能都拿同一个老的种子来生成新的种子,这时就会导致多个线程生成的同一个新种子,进而导致多线程生成的随机数相同,这并不是我们想要的。

Random生成随机数不具有原子性,只有保证其原子性,才能保证在多线程下产生的随机数是随机的。       

Random函数使用一个原子变量达到了这个效果,在创建Random对象时初始化的种子就被保存到了种子原子变量中,比如Random的next()方法,其源码如下:

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));
 }

每个Random实例里面都有一个原子性的种子变量用来记录当前的种子值,当要生成新的随机数时需要根据当前种子计算新的种子并更新回原子变量。

在多线程环境下使用单个Random实例生成随机数时,当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这会降低并发性能,为了解决这个问题引入了ThreadLocalRandom类

Random的缺点是多个线程会使用同一个原子性种子变量,从而导致对原子变量更新的竞争,而降低并发性能。


二、ThreadLocalRandom

获取一个随机数生成器:ThreadLocalRandom random = ThreadLocalRandom.current();

current()方法用来获取ThreadLocalRandom实例,并初始化调用线程中的threadLocalRandomSeed和threadLocalRandomProbe变量。   


ThreadLocalRandom的实现原理与ThreadLocal的实现原理相同。     

ThreadLocal通过让每一个线程复制一份变量,使得在每个线程对变量进行操作时实际是操作自己本地内存里面的副本,从而避免了对共享变量进行同步。


使用ThreadLocalRandom使得每个线程都维护自己的一个种子变量,每个线程在生成随机数时都根据自己老的种子计算新的种子,并使用新的种子更新老的种子,再根据新种子生成随机数,就不会存在竞争问题了,从而提高了并发性能。


三、ThreadLocalRandom的源码解析

从ThreadLocalRandom的源码可以看出,ThreadLocalRandom继承了Random类,并重写了它的nextInt()方法,在ThreadLocalRandom类中并没有使用继承自Random类的原子性种子变量。    

在ThreadLocalRandom中并没有存放具体的种子,具体的种子存放在具体的调用线程的threadLocalRandomSeed变量中。   

当线程调用ThreadLocalRandom的current方法时,ThreadLocalRandom负责初始化调用线程的threadLocalRandomSeed变量,也就是初始化种子。         


当调用ThreadLocalRandom的nextInt()方法时,实际上是获取当前线程的threadLocalRandomSeed变量作为当前种子来计算新的种子,然后更新新的种子到当前线程的threadLocalRandomSeed变量中,

而后再根据新种子并使用具体算法计算随机数。      

threadLocalRandomSeed变量就是Thread类里面的一个普通long变量,它并不是原子变量。

ThreadLocalRandom中有两个原子性变量,定义如下:

1. private static final AtomicInteger probeGenerator = new AtomicInteger();

2. private static final AtomicLong seeder = new AtomicLong(initialSeed());

在初始化调用线程的种子和探针变量时会使用到它们,每个线程只会使用一次。


关于ThreadLocalRandom来生成随机数的其他类型也是同理。不再多说。

示例代码:

import java.util.concurrent.ThreadLocalRandom;

/**
 * @ClassName: ThreadLocalRamdomTest
 * @Description: 使用ThreadLocalRandom类生成随机数
 * @Author: liuhefei
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadLocalRamdomTest {

    public static void main(String[] args) {
        //1. 获取一个随机数生成器
        ThreadLocalRandom random = ThreadLocalRandom.current();
        //2. 随机生成0到5之间的随机数(包含0, 不包含5)
        for(int i = 0; i < 10; i++){
            System.out.println(random.nextInt(5));
            System.out.println(random.nextDouble(10));
        }
    }
}


点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消