通俗易懂的JUC源码剖析-ThreadLocalRandom

网络测
• 阅读 1911

一、为什么要用ThreadLocalRandom?Random不够用吗?

我们对Random可能比较熟悉,随机数生成的常用类。来回顾下Random的用法:

Random random = new Random();
// 输出一个0~5的随机数(包括0,不包括5)
System.out.println(random.nextInt(5));

来看下nextInt的源码:

public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    // 1.根据老的种子生成新的种子
    int r = next(31);
    int m = bound - 1;
    // 2.根据新的种子计算随机数
    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;
}

再来看下根据老的种子生成新种子的代码:

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));
    return (int)(nextseed >>> (48 - bits));
}

可以看到,种子seed是用AtomicLong类型保存的,它是线程安全的,也就是说多个线程用CAS操作更新种子的时候,同一时刻只有一个线程更新成功,其他线程会循环重试。这虽然保证了线程安全,但高并发时,会有大量线程在不停地自璇重试,这无疑降低了性能。所以,ThreadLocalRandom应运而生。

二、ThreadLocalRandom的实现原理

在探究实现原理前,先看看它的用法:

ThreadLocalRandom random = ThreadLocalRandom.current();
random.next(5);

先来看看它的类结构:

public class ThreadLocalRandom extends Random {
}

可以看到,它继承了Random,但跟ThreadLocal是怎么联系上的呢?
我们可以猜想下,ThreadLocal是多个线程拥有自己的变量副本,那么ThreadLocalRandom是不是也是这个思路,多个线程都拥有自己的seed种子变量呢?这样就不用竞争同一个seed,从而提升性能。

往下继续看源码,来证实我们的猜想:

先看下它的重要属性

private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        SEED = UNSAFE.objectFieldOffset
                   (tk.getDeclaredField("threadLocalRandomSeed"));
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomProbe"));
        SECONDARY = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
     } catch (Exception e) {
        throw new Error(e);
     }
}

可以看到,SEED、PROBE、SECONDARY三个属性值都是Thread类里相应属性的偏移量。后面会分析它们的作用。

再来看看ThreadLocalRandom.curren()方法:

public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        // 初始化
        localInit();
    // instance是饿汉式的单例
    // static final ThreadLocalRandom instance = new ThreadLocalRandom();
    return instance;
}

来看下localInit()代码:

static final void localInit() {
    int p = probeGenerator.addAndGet(PROBE_INCREMENT);
    // 初始化probe值,跳过0
    int probe = (p == 0) ? 1 : p; // skip 0
    // 初始seed值
    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    Thread t = Thread.currentThread();
    // 将probe和seed值设置到当前线程实例t中
    UNSAFE.putLong(t, SEED, seed);
    UNSAFE.putInt(t, PROBE, probe);
}

再来看看nextInt()方法:

public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    // 1.根据老种子计算新种子    
    int r = mix32(nextSeed());
    // 2.根据新种子计算随机数
    int m = bound - 1;
    if ((bound & m) == 0) // power of two
       r &= m;
    else { // reject over-represented candidates
       for (int u = r >>> 1;
            u + m - (r = u % bound) < 0;
            u = mix32(nextSeed()) >>> 1)
            ;
    }
    return r;
}

可以看到,上述步骤和Random相似,关键在nextSeed()里面:

final long nextSeed() {
    Thread t; long r; // read and update per-thread seed
    // 获取当前线程t的seed旧值,增加GAMMA值后,修改回当前线程t,返回新种子的值r
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
    r = UNSAFE.getLong(t, SEED) + GAMMA);
    return r;
}

可以看到,每个线程操作的种子都是自己线程绑定的threadLocalRandomSeed,不会和其他线程产生竞争,因此提升了性能。

三、总结

最后提下SEED、PROBE、SECONDARY三个属性值的作用。
Thread类里面有这3个属性的简单注释:

/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;

SEED很显然:就是本文说的随机数种子。

PROBE:非0的long类型值。翻译过来是线程探针,在本文好像没有发挥重要作用,但是在其他类里面,比如LongAdder、ConcurrentHashMap里面都会用到这个probe,这个探针的作用是哈希线程,将线程和数组中的不同元素对应起来,尽量避免线程争用同一数组元素。可以翻看我的另一篇文章关于LongAdder的源码分析。

SECONDARY:翻译过来是第二种子,在ConcurrentSkipListMap里面
会用到,之后的文章里面会分析它的作用,请持续关注^_^

参考资料:
《Java并发编程之美》

点赞
收藏
评论区
推荐文章
Karen110 Karen110
3年前
一篇文章带你了解JavaScript随机数
一、Math.random()Math.random()返回0到1之间的随机数(包括0,不包括1)。语法:Math.random();//returnsarandomnumber代码:项目JavaScriptMath.random()单击按钮以显示0(含)和1(不含)之间的随
liam liam
2年前
Mock 语法讲解
是生成随机数据,拦截Ajax请求的JavaScript库。本文来介绍下Mock的常用语法。模拟数据生成随机数据Mock.Random.boolean()生成一个随机的布尔值。例如:返回值为true或false。Mock.Random.integer(min
Wesley13 Wesley13
3年前
Java Random类、ThreadLocalRandom类
Random和ThreadLocalRandom类均用于生成伪随机数。Random的构造函数:Random()   默认以系统当前时间为种子,相当于Random(System.currentTimeMillis())Random(long seed)常用方法:nextXxx()  生成对应类型的伪随机数。示例:Rando
Stella981 Stella981
3年前
Fortran根据系统时间生成随机数
FORTRAN中用于产生随机数的子程序有random\_seed和random\_number,其中random\_seed产生seed,random\_number根据seed的值产生随机数。当random\_seed()的参数为空时,其会给出一个默认的seed值,这意味着每次调用时产生的随机数都是相同的。(这也是有用的,这意味着你每次在执行程序的时候,会
Wesley13 Wesley13
3年前
Java 生成随机数
Java中常用的两种产生随机数的方法一、java.lang.Math类中的random()方法;调用这个Math.random()函数能够返回带正号的double值,该值大于等于0.0且小于1.0,即取值范围是\0.0,1.0)的左闭右开区间,返回值是一个伪随机选择的数,在该范围内(近似)均匀
Stella981 Stella981
3年前
Fortify漏洞之Insecure Randomness(不安全随机数)
继续对Fortify的漏洞进行总结,本篇主要针对InsecureRandomness漏洞进行总结,如下:1、InsecureRandomness(不安全随机数)1.1、产生原因:  成弱随机数的函数是random()。   电脑是一种具有确定性的机器,因此不可能
Wesley13 Wesley13
3年前
3springboot:springboot配置文件(配置文件占位符、Profile、配置文件的加载位置)
1.配置文件占位符RaandomValuePropertySourcr:配置文件可以使用随机数    ${random.value}    ${random.int} ${random.long}${random.int(10)}   ${random.int\1024,65535\}属性配置占位符
Stella981 Stella981
3年前
Spring Boot 配置随机数技巧
SpringBoot支持在系统加载的时候配置随机数。添加config/random.properties文件,添加以下内容:随机32位MD5字符串user.random.secret${random.value}随机int数字user.random.intNumber${random.int}
小万哥 小万哥
1年前
NumPy 随机数据分布与 Seaborn 可视化详解
随机数据分布什么是数据分布?数据分布是指数据集中所有可能值出现的频率,并用概率来表示。它描述了数据取值的可能性。在统计学和数据科学中,数据分布是分析数据的重要基础。NumPy中的随机分布NumPy的random模块提供了多种方法来生成服从不同分布的随机数。
小万哥 小万哥
1年前
NumPy 均匀分布模拟及 Seaborn 可视化教程
本文介绍了均匀分布和逻辑分布。均匀分布是连续概率分布,所有事件在指定范围内有相等概率发生,常用于随机数生成。其概率密度函数为f(x)1/(ba),其中a和b分别为下限和上限。NumPy的random.uniform()可生成均匀分布的随机数。Seaborn可用于可视化分布。文中还提供了练习及解决方案,包括生成不同范围的均匀分布随机数、比较分布形状变化及模拟抛硬币实验。逻辑分布则常用于S形增长现象的建模,其PDF为(scale/(π(1(xloc)/scale)^2)),由位置参数loc和尺度参数scale定义。
小万哥 小万哥
1年前
卡方分布和 Zipf 分布模拟及 Seaborn 可视化教程
卡方分布是统计学中的一种连续概率分布,用于假设检验,形状由自由度(df)决定。自由度越大,分布越平缓。NumPy的random.chisquare()可生成卡方分布随机数。Seaborn能可视化卡方分布。练习包括模拟不同自由度的卡方分布、进行卡方检验。瑞利分布描述信号处理中幅度分布,参数为尺度(scale)。Zipf分布常用于自然语言等幂律特征数据,参数a控制形状。NumPy的random.zipf()生成Zipf分布随机数。