ThreadLocal 能做什么
ThreadLocal 是 JDK 提供的一個工具類,它可以為每個使用它的線程創(chuàng)建一個線程本地的副本,從而能保證多個線程在訪問時的安全問題。當(dāng)多個線程在使用這個變量時,其實是在使用自己線程本地內(nèi)存的變量,由于是線程級別的,因此就能完全避免多個線程訪問時,資源競爭的安全問題。
ThreadLocal 的原理
要講解 ThreadLocal 的原理,我們首先需要看 JDK 的源碼,了解了 ThreadLocal 類的主要方法實現(xiàn)就能理解其原理了。使用 ThreadLocal 時主要就是調(diào)用它的兩個方法即 get 方法和 set 方法,下面直接貼出他們的源碼。
首先看 set 方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
由于 ThreadLocal 是一個帶泛型的類,所以在創(chuàng)建 ThreadLocal 對象時,需要指定對應(yīng)的泛型而 set 方法的入?yún)⒕褪嵌x的泛型對象;
接下來看具體的邏輯:
1首先創(chuàng)建了一個當(dāng)前調(diào)用 set 方法的線程實例對象;
2、調(diào)用 getMap 方法,入?yún)楫?dāng)前線程的實例對象,返回一個當(dāng)前線程對象的 threadLocals 屬性值,threadLocals 是Thread 類的一個屬性,也就是說 Java 的所有線程對象都有這個屬性,這個屬性是一個 ThreadLocalMap 對象(ThreadLocalMap 其實就是一個簡化版的 HashMap);
3、如果返回的 ThreadLocalMap 為 null(threadLocals 屬性的默認值就是 null),則會創(chuàng)建一個 ThreadLocalMap 對象,會調(diào)用createMap 方法;
4、createMap 方法接收兩個入?yún)ⅲ粋€為 當(dāng)前調(diào)用線程的實例對象,一個為 firstValue(默認 firstValue 為 null)因此這個方法會創(chuàng)建一個 key 為當(dāng)前調(diào)用線程實例,value 為當(dāng)前 set 方法傳入的值 的ThreadLocalMap 對象,并將這個對象賦值給當(dāng)前線程實例的 threadLocals 屬性;
總結(jié):要想了解 set 方法,就需要了解 threadLocals 屬性,簡單理解它就是一個 key 為當(dāng)前線程實例對象的 HashMap;set 方法就是把 set 的入?yún)⒃O(shè)置到這個 hashMap 的 value 中的過程;
接下來看 get 方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
其實 get 方法和 set 方法一樣,本質(zhì)上都是操作當(dāng)前線程實例的 threadLocals 屬性,只是 get 方法是獲取該屬性的 value 值;
接下來看具體的邏輯:
1、前面的兩個操作和 set 方法一樣,獲取當(dāng)前調(diào)用線程實例的 threadLocals 屬性值;
2、如果不為空則直接返回當(dāng)前屬性值的 value 值;
3、如果為空則為當(dāng)前線程實例初始化一個 value 值為空的 threadLocals 屬性(initialValue 方法的返回值為 null);
原理總結(jié)
1、通過以上源碼介紹,其實 ThreadLocal 是一個帶泛型的類,使用的時候就是調(diào)用它的 get 方法和它的 set 方法;
2、這兩個方法的本質(zhì)就是操作當(dāng)前調(diào)用線程實例的 threadLocals 屬性值;
3、threadLocals 屬性是一個 Thread LocalMap 對象,這個對象就是一個簡單的 HashMap,而它的 key 為當(dāng)前線程的實例對象,value 為某個需要被共享的變量值;
4、由于這個值是放在當(dāng)前線程實例的一個 Map 中,因此多個線程訪問的時候是訪問自己本地的變量,因此不可能有多線程安全訪問的問題;
ThreadLocal 應(yīng)用 Demo
先上代碼
public static void main(String[] args) {
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
stringThreadLocal.set("main threadLocal");
System.out.println("main threadLocal value:"+stringThreadLocal.get());
Thread threadA = new Thread(() -> {
stringThreadLocal.set("threadA threadLocal");
System.out.println("threadA threadLocal value:" + stringThreadLocal.get());
});
Thread threadB = new Thread(() -> {
stringThreadLocal.set("threadB threadLocal");
System.out.println("threadB threadLocal value:" + stringThreadLocal.get());
});
threadA.start();
threadB.start();
}
結(jié)果
main threadLocal value:main threadLocal
threadA threadLocal value:threadA threadLocal
threadB threadLocal value:threadB threadLocal
上面是對 ThreadLocal 最簡單的使用,雖然對 ThreadLocal 來說是一個對象,并且是被三個線程共同訪問,正常情況下由于多線程的問題一定會表現(xiàn)為結(jié)果不可預(yù)知;但是由于 ThreadLocal 的特性,是當(dāng)前線程的本地副本設(shè)置屬性值,因此不會出現(xiàn)多線程安全的問題;可以用它來記錄一下上下文的一內(nèi)容信息,或者用來設(shè)置一個日志的唯一標(biāo)識 uuid 等,來標(biāo)識當(dāng)前線程的執(zhí)行日志,方便查找問題。