服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Java并发编程ThreadLocalRandom类详解

Java并发编程ThreadLocalRandom类详解

2023-02-23 14:00派大大大星 Java教程

这篇文章主要介绍了Java并发编程ThreadLocalRandom类详解,通过提出问题为什么需要ThreadLocalRandom展开详情,感兴趣的朋友可以参考一下

为什么需要ThreadLocalRandom

java.util.Random一直都是使用比较广泛的随机数生成工具类,而且java.lang.Math中的随机数生成也是使用的java.util.Random实例。

我们下面看一下java.util.Random的使用方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Random;
public class code_4_threadRandom {
    public static void main(String[] args) {
 
        Random random = new Random();
        for(int i = 0; i < 10; i++) {
            System.out.println(
                    random.nextInt(5)
            );
        }
    }
}

随机数的生成需要一个默认的种子,这个种子是一个long类型的数字,这可以通过创建Random对象时通过构造函数指定,如果不指定则在默认构造函数内部生成一个默认值。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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类中,对象初始化时的种子就被保存到了种子原子变量里。

下面看一下next()的代码:

?
1
2
3
4
5
6
7
8
9
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));
}

在上面代码中,通过CAS操作来更新种子,在多线程情况下,多个线程同时计算随机数来计算新的种子,多个线程会竞争同一个原子变量的更新操作,会造成大量线程进行自旋重试,降低并发性能。所以ThreadLocalRandom应运而生。

ThreadRandom原理详解

?
1
2
3
4
5
6
7
8
9
10
11
import java.util.Random;
public class code_4_threadLocalRandom {
    public static void main(String[] args) {
        Random random = new ThreadLocalRandom.current();
        for(int i = 0; i < 10; i++) {
            System.out.println(
                    random1.nextInt(5)
            );
        }
    }
}

如果每个线程都维护一个种子变量,则每个线程生成随机数时都根据自己老的种子计算新的种子,并使用新的种子更新老种子,再根据新种子计算随机数,这就不会存在竞争问题了。ThreadLocalRandom 类 继 承 了 Random 类 并 重 写 了 nextlnt方法,在 ThreadLocalRandom 类中并没有使用继承自Random 类的原子性种子变量。

在ThreadLocalRandom中并没有存放具体的种子,具体的种子存放在具体的调用线程的 threadLocalRandomSeed 变量里面。ThreadLocalRandom 类似于 ThreadLocal 类,就是个工具类。当线程调用 ThreadLocalRandom的current 方法时,ThreadLocalRandom 负责初始化调用线程的threadLocalRandomSeed 变量,也就是初始化种子。当 调 用 ThreadLocalRandom 的 nextInt 方 法 时, 实际 上 是 获 取 当前 线 程的threadLocalRandomSeed 变量作为当前种子来计算新的种子,然后更新新的种子到当前线程的threadLocalRandomSeed 变量,而后再根据新种子并使用具体算法计算随机数。这里需要注意的是,threadLocalRandomSeed 变量就是 Thread 类里面的一个普通 long 变量,它并不是原子性变量。其实道理很简单,因为这个变量是线程级别的,所以根本不需要使用原子性变量。

变量instance是ThreadLocalRandom的一个实例,该变量是static的。当多线程通过ThreadLocalRandom的current方法获取ThreadLocalRandom的实例时,其实是同一个实例。但是由于具体的种子是存放在线程里面的,所以在ThreadLocalRandom的实例里面只包含与线程无关的通用算法,所以它是线程安全的。

到此这篇关于Java并发编程ThreadLocalRandom类详解的文章就介绍到这了,更多相关Java ThreadLocalRandom 内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://juejin.cn/post/7030731161580077086

延伸 · 阅读

精彩推荐