Java中的Random和ThreadLocalRandom詳細(xì)解析
一、Random類
1、簡介
Random 類用于生成偽隨機(jī)數(shù)的流。 該類使用48位種子,其使用線性同余公式進(jìn)行修改
- Math.random()使用起來相對更簡單,但不是線程安全的;
- java.util.Random的Random類是線程安全的。 但是跨線程的同時使用java.util.Random實(shí)例可能會遇到爭用,從而導(dǎo)致性能下降。 在多線程設(shè)計中考慮使用ThreadLocalRandom類;
- java.util.Random的Random不是加密安全的。 考慮使用SecureRandom獲取一個加密安全的偽隨機(jī)數(shù)生成器,供安全敏感應(yīng)用程序使用
2、Random的構(gòu)造函數(shù)
Random():創(chuàng)建一個新的隨機(jī)數(shù)生成器
/** * Creates a new random number generator. This constructor sets * the seed of the random number generator to a value very likely * to be distinct from any other invocation of this constructor. */ public Random() { //System.nanoTime()返回正在運(yùn)行的Java虛擬機(jī)的高分辨率時間源的當(dāng)前值,以納秒為單位。 //這里會調(diào)用有參構(gòu)造函數(shù) this(seedUniquifier() ^ System.nanoTime()); }
private static long seedUniquifier() { // 線性同余生成元表 for (;;) { long current = seedUniquifier.get(); long next = current * 181783497276652981L; // 兩個很大的數(shù)相乘 if (seedUniquifier.compareAndSet(current, next)) // 這個比較并且交換CAS // 比較當(dāng)前工作內(nèi)存中的值和主內(nèi)存中的值,如果這個值是期望的,那么則執(zhí)行操作! //如果不是就一直循環(huán)!就是為了保證即使在多線程的環(huán)境中返回的也是不同的數(shù) return next; } } // atomic 這個是 juc 里面修飾的原子性的 long ,get方法說就是獲得這個構(gòu)造函數(shù)里面的值 private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
random(long seed):使用單個 Long 種子創(chuàng)建一個新的隨機(jī)數(shù)生成器
偽隨機(jī)使用了線性同余法(具體可自行查閱資料)
public Random(long seed) { if (getClass() == Random.class) this.seed = new AtomicLong(initialScramble(seed)); else { // 子類可能重寫了這個不考慮 this.seed = new AtomicLong(); // 創(chuàng)建一個新的AtomicLong,初始值為 0 setSeed(seed); } } //清除nextGaussian()使用的haveNextNextGaussian 標(biāo)志, synchronized public void setSeed(long seed) { this.seed.set(initialScramble(seed)); haveNextNextGaussian = false; } private static long initialScramble(long seed) { return (seed ^ multiplier) & mask; } private static final long multiplier = 0x5DEECE66DL; // x & [(1L << 48)–1]與 x(mod 2^48)等價 取低位48位 // 帶符號左移 private static final long mask = (1L << 48) - 1;
3、next()核心方法
Random在多線程的環(huán)境是并發(fā)安全的,它解決競爭的方式是使用用原子類,本質(zhì)上上也就是CAS + Volatile保證線程安全
在Random類中,有一個AtomicLong的域,用來保存隨機(jī)種子。其中每次生成隨機(jī)數(shù)時都會根據(jù)隨機(jī)種子做移位操作以得到隨機(jī)數(shù)。
//Long類型的隨機(jī) //long類型在Java中總弄64bit,對next方法的返回值左移32作為long的高位,然后將next方法返回值作為低32位,作為long類型的隨機(jī)數(shù)。 //此處關(guān)鍵之處在于next方法 public long nextLong() { return ((long)(next(32)) << 32) + next(32); }
以下是next方法的核心,使用seed種子,不斷生成新的種子,然后使用CAS將其更新,再返回種子的移位后值。這里不斷的循環(huán)CAS操作種子,直到成功??梢?,Random實(shí)現(xiàn)原理主要是利用隨機(jī)種子采用一定算法進(jìn)行處理生成隨機(jī)數(shù),在隨機(jī)種子的安全保證利用原子類AtomicLong。
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)); }
4、Random在并發(fā)下的缺點(diǎn)
雖然Random是線程安全,但是對于并發(fā)處理使用原子類AtomicLong在大量競爭時,使用同一個 Random 對象可能會導(dǎo)致線程阻塞,由于很多CAS操作會造成失敗,不斷的Spin,而造成CPU開銷比較大而且吞吐量也會下降。
這里可以自行多線程測試,可以發(fā)現(xiàn)隨著線程增加,Random隨著競爭越來越激烈,然后耗時越來越多。然而ThreadLocalRandom隨著線程數(shù)的增加,基本沒有變化。所以在大并發(fā)的情況下,隨機(jī)的選擇,可以考慮ThreadLocalRandom提升性能。
二、ThreadLocalRandom
1、簡介
ThreadLocalRandom是Random的子類,它是將Seed隨機(jī)種子隔離到當(dāng)前線程的隨機(jī)數(shù)生成器,從而解決了Random在Seed上競爭的問題,它的處理思想和ThreadLocal本質(zhì)相同。
Unsafe 類內(nèi)的方法透露著一股 “Unsafe” 的氣息,具體表現(xiàn)就是可以直接操作內(nèi)存,而不做任何安全校驗(yàn),如果有問題,則會在運(yùn)行時拋出 Fatal Error,導(dǎo)致整個虛擬機(jī)的退出。
2、原理分析
2.1 ThreadLocalRandom單例模式
從下述代碼可以發(fā)現(xiàn)ThreadLocalRandom使用了單例模式,即在一個Java應(yīng)用中只有一個ThreadLocalRandom對象。
當(dāng)UNSAFE.getInt(Thread.currentThread(), PROBE)返回0時,就執(zhí)行l(wèi)ocalInit(),最后返回單例。
// 一個java應(yīng)用只有一個實(shí)例 // ThreadLocalRandom對象通過ThreadLocalRandom.current()獲取,之后可以直接返回隨機(jī)數(shù) static final ThreadLocalRandom instance = new ThreadLocalRandom(); //獲取ThreadLocalRandom對象實(shí)例 public static ThreadLocalRandom current() { if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0) localInit(); return instance; }
2.2 Seed隨機(jī)種子隔離到當(dāng)前線程
核心方法
//會從 object 對象var1的內(nèi)存地址偏移var2后的位置讀取四個字節(jié)作為long型返回 public native long getLong(Object var1, long var2); //可以將object對象var1的內(nèi)存地址偏移var2后的位置后四個字節(jié)設(shè)置為 var4 public native void putLong(Object var1, long var2, long var4);
UNSAFE.getInt(Thread.currentThread(), PROBE)是獲取當(dāng)前Thread線程對象中的PROBE。
首先獲取變量名SEED、PROBE等參數(shù)相對對象的偏移位置
// 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); } }
當(dāng)?shù)谝淮握{(diào)用ThreadLocalRandom.current()方法時當(dāng)前線程檢測到PROBE未初始化會調(diào)用localInit()方法進(jìn)行初始化,并把當(dāng)前線程的seed值和probe值存儲在當(dāng)前線程Thread對象內(nèi)存地址偏移相對應(yīng)變量的位置
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); }
threadLocalRandomProbe用于表示當(dāng)前線程Thread是否初始化,如果是非0,表示其已經(jīng)初始化。
換句話說,該變量就是狀態(tài)變量,用于標(biāo)識當(dāng)前線程Thread是否被初始化。
threadLocalRandomSeed從注釋中也可以看出,它是當(dāng)前線程的隨機(jī)種子。
隨機(jī)種子分散在各個Thread對象中,從而避免了并發(fā)時的競爭點(diǎn)。
3、nextSeed()核心方法
nextSeed()生成隨機(jī)種子用來生成隨機(jī)數(shù)序列
public long nextLong() { return mix64(nextSeed()); }
因?yàn)樵诔跏蓟臅r候已經(jīng)存儲了當(dāng)前線程的seed值和probe值到相應(yīng)線程對象內(nèi)存地址的偏移位置,調(diào)用nextSeed()時直接從當(dāng)前線程對象偏移位置處進(jìn)行獲取,并生成下一個隨機(jī)數(shù)種子到該位置,同時使用了UNSAFE類方法,不同線程間不需要競爭獲得seed值,因此可以可以將競爭點(diǎn)隔離
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; }
三、總結(jié)
Random是Java中提供的隨機(jī)數(shù)生成器工具類,但是在大并發(fā)的情況下由于其隨機(jī)種子的競爭會導(dǎo)致吞吐量下降,從而引入ThreadLocalRandom它將競爭點(diǎn)隔離到每個線程中,從而消除了大并發(fā)情況下競爭問題,提升了性能。
并發(fā)競爭的整體優(yōu)化思路:lock -> cas + volatile -> free lock
到此這篇關(guān)于Java中的Random和ThreadLocalRandom詳細(xì)解析的文章就介紹到這了,更多相關(guān)Random和ThreadLocalRandom解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)發(fā)送驗(yàn)證碼功能(圖片驗(yàn)證碼)
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)發(fā)送驗(yàn)證碼功能(圖片驗(yàn)證碼),本次內(nèi)容主要學(xué)習(xí)如何做一個發(fā)送驗(yàn)證碼和識別驗(yàn)證碼的功能,需要的朋友可以參考下2024-06-06SpringBoot項目中如何動態(tài)切換數(shù)據(jù)源、數(shù)據(jù)庫
本文主要介紹了SpringBoot項目中如何動態(tài)切換數(shù)據(jù)源、數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02MyBatis攔截器實(shí)現(xiàn)分頁功能實(shí)例
本篇文章主要介紹了MyBatis攔截器實(shí)現(xiàn)分頁功能實(shí)例,這里整理了詳細(xì)的代碼,有需要的小伙伴可以參考下。2017-04-04