ThreadLocalRandom隨機數源碼分析

歡迎關注公衆號【sharedCode】致力於主流中間件的源碼分析, 我的網站:https://www.shared-code.com/java

###ThreadLocalRandom介紹 ThreadLocalRandom是JDK1.7之後提供出來的一個隨機數生成工具類,性能比傳統的Math.random()更高。算法

性能比較

ThreadLocalRandom和Math.random()的性能比較,測試步驟以下:併發

public class Test {

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(10);
        long startTime = System.currentTimeMillis();
        //Math.random()  開啓10個線程
        for (int i = 0; i < 10 ; i++){
            Thread thread = new Thread(() -> {
                // 循環10000次
                for (int j = 0; j < 10000 ; j++) {
                    int x = (int) Math.random();
                }
                countDownLatch.countDown();
            });
            thread.start();
        }
        countDownLatch.await();
        System.out.println("Math.random() 耗時 = " + (System.currentTimeMillis()-startTime));
        long startTimeLocal = System.currentTimeMillis();
       // ThreadLocalRandom  開啓10個線程
        for (int i = 0; i < 10 ; i++){
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 10000 ; j++) {
                    int x = ThreadLocalRandom.current().nextInt();
                }
            });
            thread.start();
        }
        System.out.println("ThreadLocalRandom 耗時 = " + (System.currentTimeMillis()-startTimeLocal));

    }
}

測試結果以下:dom

第一次運行:
Math.random() 耗時 = 112
ThreadLocalRandom 耗時 = 7 

第二次運行:
Math.random() 耗時 = 115
ThreadLocalRandom 耗時 = 15  
  
第三次運行:
Math.random() 耗時 = 94
ThreadLocalRandom 耗時 = 9  
 
第四次運行:
Math.random() 耗時 = 126
ThreadLocalRandom 耗時 = 16

經過上面的總結,咱們能夠很清晰看到,ThreadLocalRandom的性能優秀8-10倍高併發

源碼分析

public static ThreadLocalRandom current() {
         // 調用unsafe類裏面經過這個方法是從當前對象中獲取偏移offset的值
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            // 等於0 ,說明當前線程沒有初始化,調用初始化方法。
            localInit();
        return instance;
    }
/**
* 該方法,主要是將當前線程,seed對象,放入到MAP結構中,
*/
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);
    }

下面咱們看一下nextInt 這個方法。工具

public int nextInt() {
        return mix32(nextSeed());
}

/**
 * 經過當前線程,拿到seed , 返回出去,用做隨機數計算
 */
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;
}

隨機數的一個核心參數就是seed值的 , seed就是隨機算法中的種子 , 經過這個種子和一個未知的數"0xff51afd7ed558ccdL" 相乘,而後獲得一個未知的數,根據不一樣的類型截取不一樣的長度,如int, long 等數據類型。源碼分析

###Math.Random實現性能

protected int next(int bits) {
        long oldseed, nextseed;
        // 多個線程獲取到的是同一個seed對象
        AtomicLong seed = this.seed;
        do {
            // 當前值
            oldseed = seed.get();
            // 下一個值經過某種計算出來
            nextseed = (oldseed * multiplier + addend) & mask;
          // 經過CAS操做去更新,保證更新正確
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

從上面很清晰的就看到了,原始的實現是全部的線程共享一個seed對象,雖然用的是CAS去更新,可是do while裏面仍是經過循環是重試,在高併發場景下是很耗費資源的。測試

總結:網站

ThreadLocalRandom是經過每一個線程獲取本身的seed對象,不存在線程之間的資源競爭,所以速度那是快的飛起。

相關文章
相關標籤/搜索