首頁 > 科技

硬核!ThreadLocalRandom類原理剖析火了,Github 上獲贊 79.8K

2021-06-11 15:07:46

ThreadLocalRandom類是JDK 7在JUC包下新增的隨機數生成器,它彌補了Random類在多執行緒下的缺陷。本文講解為何要在JUC下新增該類,以及該類的實現原理。

一、Random類及其侷限性

在JDK 7之前包括現在,java.util.Random 都是使用比較廣泛的隨機數生成工具類,

而且java.lang.Math中的隨機數生成也使用的是java.util.Random的例項。下面先看看java.

util. Random的使用方法。

程式碼(1)創建一個預設隨機數生成器,並使用預設的種子。

程式碼(2)輸出10個在0~5 (包含0,不包含5)之間的隨機數。

隨機數的生成需要-一個預設的種子,這個種子其實是--個long類型的數字,你可以在

創建Random物件時通過建構函式指定,如果不指定則在預設建構函式內部生成一個預設

的值。有了預設的種子後,如何生成隨機數呢?

二、ThreadL ocalRandom

為了彌補多執行緒高併發情況下Random的缺陷,在JUC包下新增了ThreadL ocalRandom

類。下面首先看下如何使用它。

三、源碼分析

從圖中可以看出ThreadLocalRandom類繼承了Random類並重寫了nextInt方法,在ThreadLocalRandom類中並沒有使用繼承自Random類的原子性種子變數。在ThreadL,ocalRandom中並沒有存放具體的種子,具體的種子存放在具體的呼叫執行緒的threadL ocalRandomSeed變數裡面。ThreadI ocalRandom類似於ThreadLocal類,就是個工具類。當執行緒呼叫ThreadLocalRandom的current方法時,ThreadLocalRandom 負責初始化呼叫執行緒的threadLocalRandomSeed變數,也就是初始化種子。

當呼叫ThreadLocalRandom的nextInt方法時,實際上是獲取當前執行緒的threadL ocalRandomSeed變數作為當前種子來計算新的種子,然後更新新的種子到當前執行緒的threadLocalRandomSeed變數,而後再根據新種子並使用具體演算法計算隨機數。這裡需要注意的是,threadLocalRandomSeed 變數就是Thread類裡面的一個普通long變數,它並不是原子性變數。其實道理很簡單,因為這個變數是執行緒級別的,所以根本不需要使用原子性變數,如果你還是不理解可以思考下ThreadLocal的原理。其中seeder和probeGenerator是兩個原子性變數,在初始化呼叫執行緒的種子和探針變數時會用到它們,每個執行緒只會使用一次。

另外,變數instance是ThreadI ocalRandom的一個例項,該變數是static 的。當多執行緒通過ThreadLocalRandom的current方法獲取ThreadLocalRandom的例項時,其實獲取的是同一個例項。但是由於具體的種子是存放線上程裡面的,所以在ThreadLocalRandom的例項裡面只包含與執行緒無關的通用演算法,所以它是執行緒安全的。

下面看看ThreadLocalRandom的主要程式碼的實現邏輯。

1.Unsafe機制

2. ThreadL ocalRandom current()方法

該方法獲取ThreadL ocalRandom例項,並初始化呼叫執行緒中的threadL ocalRandomSeed

和threadLocalRandomProbe變數。

在如上程式碼(12)中,如果當前執行緒中threadL ocalRandomProbe的變數值為0 (預設情況下執行緒的這個變數值為0),則說明當前執行緒是第一次 呼叫ThreadLocalRandom的current方法,那麼就需要呼叫locallnit方法計算當前執行緒的初始化種子變數。這裡為了延遲初始化,在不需要使用隨機數功能時就不初始化Thread類中的種子變數,這是--種優化。

程式碼(13)首先根據probeGenerator計算當前執行緒中threadL ocalRandomProbe的初始化值,然後根據seeder計算當前執行緒的初始化種子,而後把這兩個變數設定到當前執行緒。程式碼(14) 返回ThreadLocalRandom的例項。需要注意的是,這個方法是靜態方法,多個執行緒返回的是同一個ThreadI ocalRandom例項。

3. int nextlnt(int bound)方法

計算當前執行緒的下一個隨機數。

如.上程式碼的邏輯步驟與Random相似,我們重點看下nextSeed0方法。

在如上程式碼中,首先使用r= UNSAFE.getLong(t, SEED)獲取當前執行緒中threadLocalRandomSeed變數的值,然後在種子的基礎上累加GAMMA值作為新種子,而後使用UNSAFE的putI ong方法把新種子放入當前執行緒的threadI ocalRandomSeed變數中。

總結:本文章首先講解了Random的實現原理以及Random在多執行緒下需要競爭種子原子變數更新操作的缺點,從而引出ThreadLocalRandom 類。ThreadL ocalRandom使用ThreadLocal的原理,讓每個執行緒都持有一個本地的種子變數,該種子變數只有在使用隨機數時才會被初始化。在多執行緒下計算新種子時是根據自己執行緒內維護的種子變數進行更新,從而避免了競爭。


IT145.com E-mail:sddin#qq.com