ThreadLocalRandom 随机数生成器

发布于 2024-04-19 05:50:07 字数 7026 浏览 14 评论 0

ThreadLocalRandom 来自 JDK 7,是一个随机数生成器。

Random 类的局限

Random random = new Random();
for (int i = 0; i < 10; i++) {
	System.out.println(random.nextInt(5));//[0,5)
}

查看 java.util.Random#nextInt(int) 源码

    public int nextInt(int bound) {
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
            
        //根据老种子计算新种子
        int r = next(31);
        
        // 计算新种子计算随机数
        return r;
    }

java.util.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 类的实现会产生多线程下的自旋重试,降低并发性能。

ThreadLocalRandom 的诞生

使用示例

ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < 10; i++) {
	System.out.println(random.nextInt(5));
}

Random 的局限在于:多个线程竞争同一个原子性变量(种子)。

ThreadLocalRandom:每个线程都维护一个自身的本地种子,避免竞争。

ThreadLocalRandom 源码解读

public class ThreadLocalRandom extends Random

ThreadLocalRandom 继承自 Random 类,并重写部分 nextInt() 方法。

ThreadLocalRandom 的种子放于具体的调用线程 Thread 的 ThreadLocalRandomSeed 变量中。这点跟 ThreadLocal 的原理很类似。

当调用 ThreadLocalRandom.current() 时,初始化调用线程的 ThreadLocalRandomSeed 变量。

java.lang.Thread#threadLocalRandomSeed

/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;

threadLocalRandomSeed 只是一个普通的 long 变量,因为已经是线程级别,不需要考虑原子化。


java.util.concurrent.ThreadLocalRandom#seeder

/**
* The next seed for default constructors.
*/
private static final AtomicLong seeder = new AtomicLong(initialSeed());

java.util.concurrent.ThreadLocalRandom#probeGenerator

/** Generates per-thread initialization/probe field */
private static final AtomicInteger probeGenerator = new AtomicInteger();

seeder 和 probeGenerator 都是原子性变量,表示种子和探针生成器。


java.util.concurrent.ThreadLocalRandom#instance

/** The common ThreadLocalRandom */
static final ThreadLocalRandom instance = new ThreadLocalRandom();

instance 是 ThreadLocalRandom 一个静态实例,内部实现只调用了一些它的方法。


Unsafe

java.util.concurrent.ThreadLocalRandom#UNSAFE

    // Unsafe mechanics
    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);
        }
    }

static 代码块的逻辑是:利用反射机制获取 Thread 类中某些变量 threadLocalRandomXXX 的偏移量。

ThreadLocalRandom current()

    public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)//1
            localInit();//2
        return instance;//3
    }
  1. 判断当前线程中 PROBE 的值是否为 0
  2. 如果 PROBE 的值为 0,初始化当前线程的种子变量
  3. 如果 PROBE 的值不为 0,直接返回 instance 实例(ThreadLocalRandom 一个静态实例)。

java.util.concurrent.ThreadLocalRandom#localInit

    static final void localInit() {
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);
        int probe = (p == 0) ? 1 : p; // skip 0
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
        Thread t = Thread.currentThread();
        UNSAFE.putLong(t, SEED, seed);
        UNSAFE.putInt(t, PROBE, probe);
    }

根据 probe 计算 seed,然后使用 UNSAFE.put 方法设置到当前线程。


java.util.concurrent.ThreadLocalRandom#nextInt(int)

    public int nextInt(int bound) {
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
        int r = mix32(nextSeed());
        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 中的实现很类似。


java.util.concurrent.ThreadLocalRandom#nextSeed

   private static final long GAMMA = 0x9e3779b97f4a7c15L; 

   final long nextSeed() {
        Thread t; long r; // read and update per-thread seed
        UNSAFE.putLong(t = Thread.currentThread(), SEED,
                       r = UNSAFE.getLong(t, SEED) + GAMMA);
        return r;
    }

r = UNSAFE.getLong(t, SEED) + GAMMA 获取旧种子,加上 GAMMA 作为新种子。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

就像说晚安

暂无简介

0 文章
0 评论
21 人气
更多

推荐作者

qq_E2Iff7

文章 0 评论 0

Archangel

文章 0 评论 0

freedog

文章 0 评论 0

Hunk

文章 0 评论 0

18819270189

文章 0 评论 0

wenkai

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文