Java中的Random和ThreadLocalRandom詳細解析
一、Random類
1、簡介
Random 類用于生成偽隨機數(shù)的流。 該類使用48位種子,其使用線性同余公式進行修改
- Math.random()使用起來相對更簡單,但不是線程安全的;
- java.util.Random的Random類是線程安全的。 但是跨線程的同時使用java.util.Random實例可能會遇到爭用,從而導(dǎo)致性能下降。 在多線程設(shè)計中考慮使用ThreadLocalRandom類;
- java.util.Random的Random不是加密安全的。 考慮使用SecureRandom獲取一個加密安全的偽隨機數(shù)生成器,供安全敏感應(yīng)用程序使用
2、Random的構(gòu)造函數(shù)
Random():創(chuàng)建一個新的隨機數(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()返回正在運行的Java虛擬機的高分辨率時間源的當(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)建一個新的隨機數(shù)生成器
偽隨機使用了線性同余法(具體可自行查閱資料)
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的域,用來保存隨機種子。其中每次生成隨機數(shù)時都會根據(jù)隨機種子做移位操作以得到隨機數(shù)。
//Long類型的隨機
//long類型在Java中總弄64bit,對next方法的返回值左移32作為long的高位,然后將next方法返回值作為低32位,作為long類型的隨機數(shù)。
//此處關(guān)鍵之處在于next方法
public long nextLong() {
return ((long)(next(32)) << 32) + next(32);
}以下是next方法的核心,使用seed種子,不斷生成新的種子,然后使用CAS將其更新,再返回種子的移位后值。這里不斷的循環(huán)CAS操作種子,直到成功??梢姡琑andom實現(xiàn)原理主要是利用隨機種子采用一定算法進行處理生成隨機數(shù),在隨機種子的安全保證利用原子類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ā)下的缺點
雖然Random是線程安全,但是對于并發(fā)處理使用原子類AtomicLong在大量競爭時,使用同一個 Random 對象可能會導(dǎo)致線程阻塞,由于很多CAS操作會造成失敗,不斷的Spin,而造成CPU開銷比較大而且吞吐量也會下降。
這里可以自行多線程測試,可以發(fā)現(xiàn)隨著線程增加,Random隨著競爭越來越激烈,然后耗時越來越多。然而ThreadLocalRandom隨著線程數(shù)的增加,基本沒有變化。所以在大并發(fā)的情況下,隨機的選擇,可以考慮ThreadLocalRandom提升性能。
二、ThreadLocalRandom
1、簡介
ThreadLocalRandom是Random的子類,它是將Seed隨機種子隔離到當(dāng)前線程的隨機數(shù)生成器,從而解決了Random在Seed上競爭的問題,它的處理思想和ThreadLocal本質(zhì)相同。
Unsafe 類內(nèi)的方法透露著一股 “Unsafe” 的氣息,具體表現(xiàn)就是可以直接操作內(nèi)存,而不做任何安全校驗,如果有問題,則會在運行時拋出 Fatal Error,導(dǎo)致整個虛擬機的退出。
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)用只有一個實例
// ThreadLocalRandom對象通過ThreadLocalRandom.current()獲取,之后可以直接返回隨機數(shù)
static final ThreadLocalRandom instance = new ThreadLocalRandom();
//獲取ThreadLocalRandom對象實例
public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}2.2 Seed隨機種子隔離到當(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()方法進行初始化,并把當(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)前線程的隨機種子。
隨機種子分散在各個Thread對象中,從而避免了并發(fā)時的競爭點。
3、nextSeed()核心方法
nextSeed()生成隨機種子用來生成隨機數(shù)序列
public long nextLong() {
return mix64(nextSeed());
}
因為在初始化的時候已經(jīng)存儲了當(dāng)前線程的seed值和probe值到相應(yīng)線程對象內(nèi)存地址的偏移位置,調(diào)用nextSeed()時直接從當(dāng)前線程對象偏移位置處進行獲取,并生成下一個隨機數(shù)種子到該位置,同時使用了UNSAFE類方法,不同線程間不需要競爭獲得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;
}三、總結(jié)
Random是Java中提供的隨機數(shù)生成器工具類,但是在大并發(fā)的情況下由于其隨機種子的競爭會導(dǎo)致吞吐量下降,從而引入ThreadLocalRandom它將競爭點隔離到每個線程中,從而消除了大并發(fā)情況下競爭問題,提升了性能。
并發(fā)競爭的整體優(yōu)化思路:lock -> cas + volatile -> free lock
到此這篇關(guān)于Java中的Random和ThreadLocalRandom詳細解析的文章就介紹到這了,更多相關(guān)Random和ThreadLocalRandom解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入理解SpringMVC的參數(shù)綁定與數(shù)據(jù)響應(yīng)機制
本文將深入探討SpringMVC的參數(shù)綁定方式,包括基本類型、對象、集合等類型的綁定方式,以及如何處理參數(shù)校驗和異常。同時,本文還將介紹SpringMVC的數(shù)據(jù)響應(yīng)機制,包括如何返回JSON、XML等格式的數(shù)據(jù),以及如何處理文件上傳和下載。2023-06-06
詳解elasticsearch之metric聚合實現(xiàn)示例
這篇文章主要為大家介紹了elasticsearch之metric聚合實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01

