前言
借用 Java 并發編程實踐中的話:編寫正確的程序并不容易,而編寫正常的并發程序就更難了。
相比于順序執行的情況,多線程的線程安全問題是微妙而且出乎意料的,因為在沒有進行適當同步的情況下多線程中各個操作的順序是不可預期的。
并發編程相比 Java 中其他知識點學習起來門檻相對較高,學習起來比較費勁,從而導致很多人望而卻步;
而無論是職場面試和高并發高流量的系統的實現卻都還離不開并發編程,從而導致能夠真正掌握并發編程的人才成為市場比較迫切需求的。
本 Chat 作為 Java 并發編程之美系列的高級篇之一,主要講解內容如下:
ThreadLocal 的實現原理,ThreadLocal 作為變量的線程隔離方式,其內部是如何做的?
InheritableThreadLocal 的實現原理,InheritableThreadLocal 是如何彌補 ThreadLocal 不支持繼承的特性?
JDK 并發包中 ThreadLocalRandom 類原理剖析,經常使用的隨機數生成器 Random 類的原理是什么?及其局限性是什么?ThreadLocalRandom 是如何利用 ThreadLocal 的原理來解決 Random 的局限性?
最后 ThreadLocal 的一個使用場景,Spring 框架中 Scope 作用域 Bean 的實現原理。
1. ThreadLocal 的實現原理
多線程訪問同一個共享變量特別容易出現并發問題,特別是多個線程需要對一個共享變量進行寫入時候,為了保證線程安全,一般需要使用者在訪問共享變量的時候進行適當的同步,如下圖:
同步的措施一般是加鎖,這就需要使用者對鎖也要有一定了解,這顯然加重了使用者的負擔。
那么有沒有一個方式當創建一個變量時候,每個線程對其進行訪問的時候訪問的是自己線程的變量呢?其實 ThreadLocal 就可以做這個事情,雖然 ThreadLocal 的出現并不是為了解決上面的問題而出現的。
ThreadLocal 是在 JDK 包里面提供的,它提供了線程本地變量,也就是如果你創建了一個 ThreadLocal 變量,那么訪問這個變量的每個線程都會有這個變量的一個本地拷貝,多個線程操作這個變量的時候,實際是操作的自己本地內存里面的變量,從而避免了線程安全問題,創建一個 ThreadLocal 變量后每個線程會拷貝一個變量到自己本地內存,如下圖:
1.1 ThreadLocal 簡單使用
本節來看下 ThreadLocal 如何使用,從而加深理解,本例子開啟了兩個線程,每個線程內部設置了本地變量的值,然后調用 print 函數打印當前本地變量的值,如果打印后調用了本地變量的 remove 方法則會刪除本地內存中的該變量,代碼如下:
public class ThreadLocalTest { //(1)打印函數
static void print(String str){ //1.1 打印當前線程本地內存中localVariable變量的值
System.out.println(str + ":" +localVariable.get()); //1.2 清除當前線程本地內存中localVariable變量
//localVariable.remove();
} //(2) 創建ThreadLocal變量
static ThreadLocal<String> localVariable = new ThreadLocal<>(); public static void main(String[] args) { //(3) 創建線程one
Thread threadOne = new Thread(new Runnable() { public void run() { //3.1 設置線程one中本地變量localVariable的值
localVariable.set("threadOne local variable"); //3.2 調用打印函數
print("threadOne"); //3.3打印本地變量值
System.out.println("threadOne remove after" + ":" +localVariable.get());
}
}); //(4) 創建線程two
Thread threadTwo = new Thread(new Runnable() { public void run() { //4.1 設置線程one中本地變量localVariable的值
localVariable.set("threadTwo local variable"); //4.2 調用打印函數
print("threadTwo"); //4.3打印本地變量值
System.out.println("threadTwo remove after" + ":" +localVariable.get());
}
}); //(5)啟動線程
threadOne.start();
threadTwo.start();
}
運行結果:
threadOne:threadOne local variable
threadTwo:threadTwo local variable
threadOne remove after:threadOne local variable
threadTwo remove after:threadTwo local variable
代碼(2)創建了一個 ThreadLocal 變量;
代碼(3)、(4)分別創建了線程 one 和 two;
代碼(5)啟動了兩個線程;
線程 one 中代碼 3.1 通過 set 方法設置了 localVariable 的值,這個設置的其實是線程 one 本地內存中的一個拷貝,這個拷貝線程 two 是訪問不了的。然后代碼 3.2 調用了 print 函數,代碼 1.1 通過 get 函數獲取了當前線程(線程 one)本地內存中 localVariable 的值;
線程 two 執行類似線程 one。
解開代碼 1.2 的注釋后,再次運行,運行結果為:
threadOne:threadOne local variable
threadOne remove after:nullthreadTwo:threadTwo local variable
threadTwo remove after:null
1.2 ThreadLocal 實現原理
首先看下 ThreadLocal 相關的類的類圖結構。
如上類圖可知 Thread 類中有一個 threadLocals 和 inheritableThreadLocals 都是 ThreadLocalMap 類型的變量,而 ThreadLocalMap 是一個定制化的 Hashmap,默認每個線程中這個兩個變量都為 null,只有當前線程第一次調用了 ThreadLocal 的 set 或者 get 方法時候才會進行創建。
其實每個線程的本地變量不是存放到 ThreadLocal 實例里面的,而是存放到調用線程的 threadLocals 變量里面。也就是說 ThreadLocal 類型的本地變量是存放到具體的線程內存空間的。
ThreadLocal 就是一個工具殼,它通過 set 方法把 value 值放入調用線程的 threadLocals 里面存放起來,當調用線程調用它的 get 方法時候再從當前線程的 threadLocals變 量里面拿出來使用。
如果調用線程一直不終止,那么這個本地變量會一直存放到調用線程的 threadLocals 變量里面,所以當不需要使用本地變量時候可以通過調用 ThreadLocal 變量的 remove 方法,從當前線程的 threadLocals 里面刪除該本地變量。
另外 Thread 里面的 threadLocals 為何設計為 map 結構呢?很明顯是因為每個線程里面可以關聯多個 ThreadLocal 變量。
下面簡單分析下 ThreadLocal 的 set,get,remove 方法的實現邏輯:
- void set(T value)
public void set(T value) { //(1)獲取當前線程
Thread t = Thread.currentThread(); //(2)當前線程作為key,去查找對應的線程變量,找到則設置
ThreadLocalMap map = getMap(t); if (map != null)
map.set(this, value); else
//(3)第一次調用則創建當前線程對應的HashMap
createMap(t, value);
}
如上代碼(1)首先獲取調用線程,然后使用當前線程作為參數調用了 getMap(t) 方法,getMap(Thread t) 代碼如下:
ThreadLocalMap getMap(Thread t) { return t.threadLocals;
}
可知 getMap(t) 所做的就是獲取線程自己的變量 threadLocals,threadlocal 變量是綁定到了線程的成員變量里面。
如果 getMap(t) 返回不為空,則把 value 值設置進入到 threadLocals,也就是把當前變量值放入了當前線程的內存變量 threadLocals,threadLocals 是個 HashMap 結構,其中 key 就是當前 ThreadLocal 的實例對象引用,value 是通過 set 方法傳遞的值。
如果 getMap(t) 返回空那說明是第一次調用 set 方法,則創建當前線程的 threadLocals 變量,下面看 createMap(t, value) 里面做了啥呢?
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可知就是創建當前線程的 threadLocals 變量。
- T get()
public T get() { //(4) 獲取當前線程
Thread t = Thread.currentThread(); //(5)獲取當前線程的threadLocals變量
ThreadLocalMap map = getMap(t); //(6)如果threadLocals不為null,則返回對應本地變量值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked")
T result = (T)e.value; return result;
}
} //(7)threadLocals為空則初始化當前線程的threadLocals成員變量
return setInitialValue();
}
如上代碼(4)首先獲取當前線程實例,如果當前線程的 threadLocals 變量不為 null 則直接返回當前線程綁定的本地變量。否者執行代碼(7)進行初始化,setInitialValue() 的代碼如下:
private T setInitialValue() { //(8)初始化為null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //(9)如果當前線程的threadLocals變量不為空
if (map != null)
map.set(this, value); else
//(10)如果當前線程的threadLocals變量為空
createMap(t, value); return value;
}
protected T initialValue() { return null;
}
如上代碼如果當前線程的 threadLocals 變量不為空,則設置當前線程的本地變量值為 null,否者調用 createMap 創建當前線程的 createMap 變量。
* void remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null)
m.remove(this);
}
如上代碼,如果當前線程的 threadLocals 變量不為空,則刪除當前線程中指定 ThreadLocal 實例的本地變量。
注:每個線程內部都有一個名字為 threadLocals 的成員變量,該變量類型為 HashMap,其中 key 為我們定義的 ThreadLocal 變量的 this 引用,value 則為我們 set 時候的值,每個線程的本地變量是存到線程自己的內存變量 threadLocals 里面的,如果當前線程一直不消失那么這些本地變量會一直存到,所以可能會造成內存泄露,所以使用完畢后要記得調用 ThreadLocal 的 remove 方法刪除對應線程的 threadLocals 中的本地變量。
#### **1.3 子線程中獲取不到父線程中設置的 ThreadLocal 變量的值**
首先看個例子說明標題的意思:
public class TestThreadLocal { //(1) 創建線程變量
public static ThreadLocal<String> threadLocal = new ThreadLocal<String>(); public static void main(String[] args) { //(2) 設置線程變量
threadLocal.set("hello world"); //(3) 啟動子線程
Thread thread = new Thread(new Runnable() { public void run() { //(4)子線程輸出線程變量的值
System.out.println("thread:" + threadLocal.get());
}
});
thread.start(); //(5)主線程輸出線程變量值
System.out.println("main:" + threadLocal.get());
}
}
結果為:
main:hello world
thread:null
也就是說同一個 ThreadLocal 變量在父線程中設置值后,在子線程中是獲取不到的。
根據上節的介紹,這個應該是正常現象,因為子線程調用 get 方法時候當前線程為子線程,而調用 set 方法設置線程變量是 main 線程,兩者是不同的線程,自然子線程訪問時候返回 null,那么有辦法讓子線程訪問到父線程中的值嗎?答案是有。
### **2\. InheritableThreadLocal 原理**
為了解決上節的問題 InheritableThreadLocal 應運而生,InheritableThreadLocal 繼承自 ThreadLocal,提供了一個特性,就是子線程可以訪問到父線程中設置的本地變量。
下面看下 InheritableThreadLocal 的代碼:
public class InheritableThreadLocal<T> extends ThreadLocal<T> { //(1)
protected T childValue(T parentValue) { return parentValue;
} //(2)
ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals;
} //(3)
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
如上代碼可知 InheritableThreadLocal 繼承了 ThreadLocal,并重寫了三個方法。
* 代碼(3)可知 InheritableThreadLocal 重寫了 createMap 方法,那么可知現在當第一次調用 set 方法時候創建的是當前線程的 inheritableThreadLocals 變量的實例而不再是 threadLocals。
* 代碼(2)可知當調用 get 方法獲取當前線程的內部 map 變量時候,獲取的是 inheritableThreadLocals 而不再是 threadLocals。
綜上可知在 InheritableThreadLocal 的世界里,線程中的變量 inheritableThreadLocals 替代了 threadLocals。
* 下面我們看下重寫的代碼(1)是何時被執行,以及如何實現的子線程可以訪問父線程本地變量的。這個要從 Thread 創建的代碼看起,Thread 的默認構造函數及 Thread.java 類的構造函數如下:
```
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
```
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
... //(4)獲取當前線程
Thread parent = currentThread();
... //(5)如果父線程的inheritableThreadLocals變量不為null
if (parent.inheritableThreadLocals != null) //(6)設置子線程中的inheritableThreadLocals變量
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize;
tid = nextThreadID();
}
創建線程時候在構造函數里面會調用 init 方法,前面講到了 inheritableThreadLocal 類 get,set 方法操作的是變量 inheritableThreadLocals,所以這里 inheritableThreadLocal 變量就不為 null,所以會執行代碼(6),下面看下 createInheritedMap 代碼:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap);
}
可知 createInheritedMap 內部使用父線程的 inheritableThreadLocals 變量作為構造函數創建了一個新的 ThreadLocalMap 變量。
然后賦值給了子線程的 inheritableThreadLocals 變量,那么下面看看 ThreadLocalMap 的構造函數里面做了什么:
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table; int len = parentTable.length;
setThreshold(len);
table = new Entry[len]; for (int j = 0; j < len; j++) {
Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { //(7)調用重寫的方法
Object value = key.childValue(e.value);//返回e.value
Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
如上代碼所做的事情就是把父線程的 inheritableThreadLocals 成員變量的值復制到新的 ThreadLocalMap 對象,其中代碼(7)InheritableThreadLocal 類重寫的代碼(1)也映入眼簾了。
總結:InheritableThreadLocal 類通過重寫代碼(2)和(3)讓本地變量保存到了具體線程的 inheritableThreadLocals 變量里面,線程通過 InheritableThreadLocal 類實例的 set 或者 get 方法設置變量時候就會創建當前線程的 inheritableThreadLocals 變量。
當父線程創建子線程時候,構造函數里面會把父線程中 inheritableThreadLocals 變量里面的本地變量拷貝一份復制到子線程的 inheritableThreadLocals 變量里面。
把上節代碼(1)修改為:
//(1) 創建線程變量
public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
運行結果為:
thread:hello world
main:hello world
可知現在可以從子線程中正常的獲取到線程變量值了。
那么什么情況下需要子線程可以獲取到父線程的 threadlocal 變量呢,情況還是蠻多的,比如存放用戶登錄信息的 threadlocal 變量,很有可能子線程中也需要使用用戶登錄信息,再比如一些中間件需要用統一的追蹤 ID 把整個調用鏈路記錄下來的情景。
### **3\. JDK 并發包中 ThreadLocalRandom 類原理剖析**
ThreadLocalRandom 類是 JDK7 在 JUC 包下新增的隨機數生成器,它解決了 Random 類在多線程下的不足。本節就來講解下 JUC 下為何新增該類,以及該類的實現原理。
#### **3.1 Random 類及其局限性**
在 JDK7 之前包括現在,java.util.Random 應該是使用比較廣泛的隨機數生成工具類,另外 java.lang.Math 中的隨機數生成也是使用的 java.util.Random 的實例。下面先看看 java.util.Random 的使用:
public class RandomTest { public static void main(String[] args) { //(1)創建一個默認種子的隨機數生成器
Random random = new Random(); //(2)輸出10個在0-5(包含0,不包含5)之間的隨機數
for (int i = 0; i < 10; ++i) {
System.out.println(random.nextInt(5));
}
}
}
* 代碼(1)創建一個默認隨機數生成器,使用默認的種子。
* 代碼(2)輸出輸出10個在0-5(包含0,不包含5)之間的隨機數。
這里提下隨機數的生成需要一個默認的種子,這個種子其實是一個 long 類型的數字,這個種子要么在 Random 的時候通過構造函數指定,那么默認構造函數內部會生成一個默認的值,有了默認的種子后,如何生成隨機數呢?
public int nextInt(int bound) { //(3)參數檢查
if (bound <= 0) throw new IllegalArgumentException(BadBound); //(4)根據老的種子生成新的種子
int r = next(31); //(5)根據新的種子計算隨機數
... return r;
}
如上代碼可知新的隨機數的生成需要兩個步驟:
* 首先需要根據老的種子生成新的種子。
* 然后根據新的種子來計算新的隨機數。
其中步驟(4)我們可以抽象為 seed=f(seed),其中 f 是一個固定的函數,比如 `seed= f(seed)=a*seed+b;`,步驟(5)也可以抽象為 g(seed,bound),其中 g 是一個固定的函數,比如 `g(seed,bound)=(int)((bound * (long)seed) >> 31);`。
在單線程情況下每次調用 nextInt 都是根據老的種子計算出來新的種子,這是可以保證隨機數產生的隨機性的。
但是在多線程下多個線程可能都拿同一個老的種子去執行步驟(4)計算新的種子,這會導致多個線程產生的新種子是一樣的,由于步驟(5)算法是固定的,所以會導致多個線程產生相同的隨機值,這并不是我們想要的。
所以需要保證步驟(4)的原子性,也就是說多個線程在根據同一個老種子計算新種子時候,第一個線程的新種子計算出來后,第二個線程要丟棄自己老的種子,要使用第一個線程的新種子來計算自己的新種子,依次類推,只有保證了這個,才能保證多線程下產生的隨機數是隨機的。
Random 函數使用一個原子變量達到了這個效果,在創建 Random 對象時候初始化的種子就保存到了種子原子變量里面,下面看下 next() 代碼:
protected int next(int bits) { long oldseed, nextseed;
AtomicLong seed = this.seed;
do { //(6)
oldseed = seed.get(); //(7)
nextseed = (oldseed * multiplier + addend) & mask; //(8)
} while (!seed.compareAndSet(oldseed, nextseed)); //(9)
return (int)(nextseed >>> (48 - bits));
}
* 代碼(6)獲取當前原子變量種子的值;
* 代碼(7)根據當前種子值計算新的種子;
* 代碼(8)使用 CAS 操作,使用新的種子去更新老的種子,多線程下可能多個線程都同時執行到了代碼(6),那么可能多個線程都拿到的當前種子的值是同一個,然后執行步驟(7)計算的新種子也都是一樣的,但是步驟(8)的 CAS 操作會保證只有一個線程可以更新老的種子為新的,失敗的線程會通過循環重新獲取更新后的種子作為當前種子去計算老的種子,可見這里解決了上面提到的問題,也就保證了隨機數的隨機性。
* 代碼(9)則使用固定算法根據新的種子計算隨機數。
總結:每個 Random 實例里面有一個原子性的種子變量用來記錄當前的種子的值,當要生成新的隨機數時候要根據當前種子計算新的種子并更新回原子變量。
多線程下使用單個 Random 實例生成隨機數時候,多個線程同時計算新的種子時候會競爭同一個原子變量的更新操作,由于原子變量的更新是 CAS 操作,同時只有一個線程會成功,所以會造成大量線程進行自旋重試,這是會降低并發性能的,所以 ThreadLocalRandom 應運而生。
#### **3.2 ThreadLocalRandom 類**
為了解決多線程高并發下 Random 的缺陷,JUC 包下新增了 ThreadLocalRandom 類,下面首先看下它如何使用:
public class RandomTest { public static void main(String[] args) { //(10)獲取一個隨機數生成器
ThreadLocalRandom random = ThreadLocalRandom.current(); //(11)輸出10個在0-5(包含0,不包含5)之間的隨機數
for (int i = 0; i < 10; ++i) {
System.out.println(random.nextInt(5));
}
}
}
如上代碼(10)調用 ThreadLocalRandom.current() 來獲取當前線程的隨機數生成器。
下面來分析下 ThreadLocalRandom 的實現原理。從名字看會讓我們聯想到《[Java 并發編程之美:基礎篇](http://mp.weixin.qq.com/s?__biz=MzIwNjEwNTQ4Mw==&mid=2651581230&idx=1&sn=2d8d0cfaaf02564ae57f135d6d682125&chksm=8cd9f5cabbae7cdccb7eaa496352421f93a81fbe948ca66a009fa509083bfef0eab781cbbecc&scene=21#wechat_redirect)》 中講解的 ThreadLocal,ThreadLocal 的出現就是為了解決多線程下變量的隔離問題,讓每一個線程拷貝一份變量,每個線程對變量進行操作時候實際是操作自己本地內存里面的拷貝。
實際上 ThreadLocalRandom 的實現也是這個原理,Random 的缺點是多個線程會使用原子性種子變量,會導致對原子變量更新的競爭,如下圖:

那么如果每個線程維護自己的一個種子變量,每個線程生成隨機數時候根據自己老的種子計算新的種子,并使用新種子更新老的種子,然后根據新種子計算隨機數,就不會存在競爭問題,這會大大提高并發性能,如下圖 ThreadLocalRandom 原理:

##### **源碼分析**
首先看下 ThreadLocalRandom 的類圖結構:

可知 ThreadLocalRandom 繼承了 Random 并重寫了 nextInt 方法,ThreadLocalRandom 中并沒有使用繼承自 Random 的原子性種子變量。
ThreadLocalRandom 中并沒有具體存放種子,具體的種子是存放到具體的調用線程的 threadLocalRandomSeed 變量里面的,ThreadLocalRandom 類似于 ThreadLocal類 就是個工具類。
當線程調用 ThreadLocalRandom 的 current 方法時候 ThreadLocalRandom 負責初始化調用線程的 threadLocalRandomSeed 變量,也就是初始化種子。
當調用 ThreadLocalRandom 的 nextInt 方法時候,實際上是獲取當前線程的 threadLocalRandomSeed 變量作為當前種子來計算新的種子,然后更新新的種子到當前線程的 threadLocalRandomSeed 變量,然后在根據新種子和具體算法計算隨機數。
這里需要注意的是 threadLocalRandomSeed 變量就是 Thread 類里面的一個普通 long 變量,并不是原子性變量,其實道理很簡單,因為這個變量是線程級別的,根本不需要使用原子性變量,如果還是不理解可以思考下 ThreadLocal 的原理。
其中變量 seeder 和 probeGenerator 是兩個原子性變量,在初始化調用線程的種子和探針變量時候用到,每個線程只會使用一次。
另外變量 instance 是個 ThreadLocalRandom 的一個實例,該變量是 static 的,當多線程通過 ThreadLocalRandom 的 current 方法獲取 ThreadLocalRandom 的實例時候其實獲取的是同一個,但是由于具體的種子是存放到線程里面的,所以 ThreadLocalRandom 的實例里面只是與線程無關的通用算法,所以是線程安全的。
下面看看 ThreadLocalRandom 的主要代碼實現邏輯。
* Unsafe 機制的使用,具體的會在高級篇之二里面講解。
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實例
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class; //獲取Thread類里面threadLocalRandomSeed變量在Thread實例里面偏移量
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed")); //獲取Thread類里面threadLocalRandomProbe變量在Thread實例里面偏移量
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe")); //獲取Thread類里面threadLocalRandomProbe變量在Thread實例里面偏移量,這個值在后面講解的LongAdder里面會用到
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception e) { throw new Error(e);
}
}
* ThreadLocalRandom current() 方法:該方法獲取 ThreadLocalRandom 實例,并初始化調用線程中 threadLocalRandomSeed 和 threadLocalRandomProbe 變量。
static final ThreadLocalRandom instance = new ThreadLocalRandom(); public static ThreadLocalRandom current() { //(12)
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0) //(13)
localInit(); //(14)
return instance;
}
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);
}
```
如上代碼(12)如果當前線程中 threadLocalRandomProbe 變量值為0(默認情況下線程的這個變量為0),說明當前線程第一次調用 ThreadLocalRandom 的 current 方法,那么就需要調用 localInit 方法計算當前線程的初始化種子變量。
這里設計為了延遲初始化,不需要使用隨機數功能時候 Thread 類中的種子變量就不需要被初始化,這是一種優化。
代碼(13)首先計算根據 probeGenerator 計算當前線程中 threadLocalRandomProbe 的初始化值,然后根據 seeder 計算當前線程的初始化種子,然后把這兩個變量設置到當前線程。
代碼(14)返回 ThreadLocalRandom 的實例,需要注意的是這個方法是靜態方法,多個線程返回的是同一個 ThreadLocalRandom 實例。
* int nextInt(int bound) 方法:計算當前線程的下一個隨機數。
```
public int nextInt(int bound) { //(15)參數校驗
if (bound <= 0) throw new IllegalArgumentException(BadBound); //(16) 根據當前線程中種子計算新種子
int r = mix32(nextSeed()); //(17)根據新種子和bound計算隨機數
int m = bound - 1; if ((bound & m) == 0) // power of two
r &= m; else { // reject over-represented candidates
for (int u = r >>> 1;
u + m - (r = u % bound) < 0;
u = mix32(nextSeed()) >>> 1)
;
} return r;
}
```
如上代碼邏輯步驟與 Random 相似,我們重點看下 nextSeed() 方法:
```
final long nextSeed() {
Thread t; long r; //
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA); return r;
}
```
如上代碼首先使用 r = UNSAFE.getLong(t, SEED) 獲取當前線程中 threadLocalRandomSeed 變量的值,然后在種子的基礎上累加 GAMMA 值作為新種子,然后使用 UNSAFE 的 putLong 方法把新種子放入當前線程的 threadLocalRandomSeed 變量。
注:本節首先講解了 Random 的實現原理以及介紹了 Random 在多線程下存在競爭種子原子變量更新操作失敗后自旋等待的缺點,從而引出 ThreadLocalRandom 類,ThreadLocalRandom 使用 ThreadLocal 的原理,讓每個線程內持有一個本地的種子變量,該種子變量只有在使用隨機數時候才會被初始化,多線程下計算新種子時候是根據自己線程內維護的種子變量進行更新,從而避免了競爭。