Random和ThreadLocalRandom都是用於生成隨機數的。用法比較簡單,這裏直接看代碼分析原理。
Random
Random隨機數的實現幾乎都是依賴於內部的next方法,都是在next的基礎上進行額外的計算,這裏直接看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));
//返回新的種子
return (int)(nextseed >>> (48 - bits));
}
Random獲取隨機數分兩步:
- 獲得老的種子數據
- 通過老的種子計算新的種子
最後,用老的種子替換新的種子,這個我們都能理解,但是這裏使用的是CAS進行替換,這個是爲什麼呢?
如果不用CAS,再看上面代碼,如果是多個線程同時並行訪問,會出現:多個線程同時獲得了同一個老的種子,然後根據老的種子計算出同樣的新的種子,這就導致了多個線程獲得的隨機數相同。
所以,介於這種情況,使用CAS來進行控制,使得在多線程的情況下,保證每個線程獲取的隨機數是不同的。如果CAS失敗,說明老的種子已經被使用,所以會繼續循環重新拿到最新的老的種子再去計算新的種子,直到成功並返回。
Random確實很好的解決了多線程下的問題,但是這又帶來了新的問題:在線程競爭激烈的情況下,多個線程通過CAS競爭一個seed,導致大多數線程都在自旋,嚴重降低了多線程下獲取隨機數的效率,所以ThreadLocalRandom就是爲了解決這個問題。
ThreadLocalRandom
初始化
ThreadLocalRandom的原理其實也很簡單。既然Random多線程共享seed,競爭激烈導致效率低,那爲什麼不每個線程都擁有自己的seed呢,每個線程每次通過自己的當前seed獲取新的seed呢?是的,ThreadLocalRandom就是這樣做的。
那每個線程如果都擁有自己的seed,首先每個線程的seed保存在哪兒呢?不用多想,當前是當前線程對象了(ThreadLocal在線程本地也有自己的ThreadLocalMap,原理是類似的)。
爲了實現ThreadLocalRandom,在Thread中有如下字段:
/** The current seed for a ThreadLocalRandom */
//用於產生隨機數的種子
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
//如果threadLocalRandomSeed被初始化則threadLocalRandomProbe不爲0
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
//用來生成threadLocalRandomSeed的輔助種子
int threadLocalRandomSecondarySeed;
從Thread中字段的定義來看,threadLocalRandomSeed是沒有初始值得。可想而知:只有用到ThreadLocalRandom的時候,才需要threadLocalRandomSeed,需要的時候再進行初始化,避免不必要的內存消耗。
ThreadLocalRandom是在第一次調用current的時候,對當前threadLocalRandomSeed進行的初始化的,並且ThreadLocalRandom是單例模式:
public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}
對上面代碼中的UNSAFE.getInt(Thread.currentThread(), PROBE)
不理解的,這裏簡單講一下unsafe相關的知識。先看這句代碼,找到UNSAFE和PROBE到底是什麼,在ThreadLocalRandom中有如下定義:
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);
}
}
可以看到,UNSAFE的類型是sun.misc.Unsafe,而PROBE,通過UNSAFE.objectFieldOffset獲取的。其實,在unsafe中,定義了很多的直接內存訪問操作,操作更高效,但是也存在一定的風險。unsafe可以通過getDeclaredField獲得某個對象中某個字段的偏移量,然後可以直接通過字段偏移量獲取某個對象的字段值,也可以通過字段偏移量給對象的字段賦值。當然,unsafe的功能不止於此,對於unsafe更詳細的介紹可以直接看 Unsafe應用解析
這裏就是,先通過static初始化拿到線程中threadLocalRandomSeed等變量的偏移量,然後在用到這些變量的時候,通過初始化得到的偏移量,使用get和put取值和賦值操作。
好,現在繼續回過頭看ThreadLocalRandom的創建:
public static ThreadLocalRandom current() {
//拿到當前線程對象中threadLocalRandomProbe的值,如果爲0(默認值就是0),說明初始種子沒有初始化,所以需要先進行初始化。
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
//instance是static,ThreadLocalRandom 是單例模式,因爲seed保存在當前的線程中,所以沒必要每次都創建一個ThreadLocalRandom
return instance;
}
static final void localInit() {
//重新計算threadLocalRandomProbe,因爲已經初始化了,所以threadLocalRandomProbe一定不能爲0了。
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p == 0) ? 1 : p; // skip 0
//計算初始種子,這裏關鍵是seeder。
//seeder是靜態變量,在類加載的時候會初始化一個最初始的值。
//這裏在計算seed的值得同時,每來一個線程進行初始化,這裏都會爲seeder的值加SEEDER_INCREMENT,保證下一個線程的seeder和前一個的不一樣。
//所以當所有線程第一次同時來競爭初始化的時候,會存在競爭,初始化之後,就不會再競爭了。
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
//將計算後的初始化值通過unsafe賦值給當前線程的變量
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
初始化種子,關鍵是seeder,seeder定義與初始化如下:
private static final AtomicLong seeder = new AtomicLong(initialSeed());
private static long initialSeed() {
//這兒可以配置啓動參數來選擇通過哪種方式進行初始化seeder。
String sec = VM.getSavedProperty("java.util.secureRandomSeed");
if (Boolean.parseBoolean(sec)) {
byte[] seedBytes = java.security.SecureRandom.getSeed(8);
long s = (long)(seedBytes[0]) & 0xffL;
for (int i = 1; i < 8; ++i)
s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
return s;
}
return (mix64(System.currentTimeMillis()) ^
mix64(System.nanoTime()));
}
可以看到,這裏seeder是靜態變量,所以在類加載的時候,就初始化好了。因爲每個線程調用localInit都會改變seeder的值,也就保證了每個線程的seeder是不一樣的。
nextSeed
ThreadLocalRandom獲取隨機數,基本都是通過nextSeed實現的,nextSeed代碼如下,和Random原理相同,分爲三步:
final long nextSeed() {
Thread t; long r; // read and update per-thread seed
//1、獲取老的種子
//2、計算新的種子
//3、將新的種子覆蓋老的種子
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
- 獲取老的種子 :
UNSAFE.getLong(t, SEED)
- 計算新的種子:
r = UNSAFE.getLong(t, SEED) + GAMMA
- 將新的種子覆蓋老的種子
UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA);
和Random的區別就是:種子保存在當前線程對象中。