您好,登錄后才能下訂單哦!
今天小編給大家分享一下Java ThreadLocal類如何使用的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
如圖所示:
接下來(lái)我們就先用一個(gè)簡(jiǎn)單的樣例給大家展示一下ThreadLocal的基本用法
package cuit.pymjl.thradlocal; /** * @author Pymjl * @version 1.0 * @date 2022/7/1 10:56 **/ public class MainTest { static ThreadLocal<String> threadLocal = new ThreadLocal<>(); static void print(String str) { //打印當(dāng)前線程中本地內(nèi)存中本地變量的值 System.out.println(str + " :" + threadLocal.get()); //清除本地內(nèi)存中的本地變量 threadLocal.remove(); } public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { //設(shè)置線程1中本地變量的值 threadLocal.set("thread1 local variable"); //調(diào)用打印方法 print("thread1"); //打印本地變量 System.out.println("after remove : " + threadLocal.get()); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { //設(shè)置線程1中本地變量的值 threadLocal.set("thread2 local variable"); //調(diào)用打印方法 print("thread2"); //打印本地變量 System.out.println("after remove : " + threadLocal.get()); } }); t1.start(); t2.start(); } }
運(yùn)行結(jié)果如圖所示:
我們先來(lái)看一下ThreadLocal 相關(guān)類的類圖結(jié)構(gòu),如圖所示:
由該圖可知, Thread 類中有一個(gè)threadLocals 和一個(gè)inheritableThreadLocals , 它們都是ThreadLocalMap 類型的變量, 而ThreadLocalMap 是一個(gè)定制化的Hashmap 。在默認(rèn)情況下, 每個(gè)線程中的這兩個(gè)變量都為null ,只有當(dāng)前線程第一次調(diào)用ThreadLocal 的set 或者get 方法時(shí)才會(huì)創(chuàng)建它們。其實(shí)每個(gè)線程的本地變量不是存放在ThreadLocal 實(shí)例里面,而是存放在調(diào)用線程的threadLocals 變量里面。也就是說(shuō), ThreadLocal 類型的本地變量存放在具體的線程內(nèi)存空間中。ThreadLocal 就是一個(gè)工具殼,它通過(guò)set 方法把value 值放入調(diào)用線程的threadLocals 里面并存放起來(lái), 當(dāng)調(diào)用線程調(diào)用它的get 方法時(shí),再?gòu)漠?dāng)前線程的threadLocals 變量里面將其拿出來(lái)使用。 如果調(diào)用線程一直不終止, 那么這個(gè)本地變量會(huì)一直存放在調(diào)用線程的threadLocals 變量里面,所以當(dāng)不需要使用本地變量時(shí)可以通過(guò)調(diào)用ThreadLocal 變量的remove 方法,從當(dāng)前線程的threadLocals 里面刪除該本地變量。另外, Thread 里面的threadLocals 為何被設(shè)計(jì)為map 結(jié)構(gòu)?很明顯是因?yàn)槊總€(gè)線程可以關(guān)聯(lián)多個(gè)ThreadLocal 變量。 接下來(lái)我們來(lái)看看ThreadLocal的set、get、以及remove的源碼
public void set(T value) { // 1.獲取當(dāng)前線程(調(diào)用者線程) Thread t = Thread.currentThread(); // 2.以當(dāng)前線程作為key值,去查找對(duì)應(yīng)的線程變量,找到對(duì)應(yīng)的map ThreadLocalMap map = getMap(t); if (map != null) { // 3.如果map不為null,則直接添加元素 map.set(this, value); } else { // 4.否則就先創(chuàng)建map,再添加元素 createMap(t, value); } }
void createMap(Thread t, T firstValue) { /** * 這里是創(chuàng)建一個(gè)ThreadLocalMap,以當(dāng)前調(diào)用線程的實(shí)例對(duì)象為key,初始值為value * 然后放入當(dāng)前線程的Therad.threadLocals屬性里面 */ t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocalMap getMap(Thread t) { //這里就是直接獲取調(diào)用線程的成員屬性threadlocals return t.threadLocals; }
public T get() { // 1.獲取當(dāng)前線程 Thread t = Thread.currentThread(); // 2.獲取當(dāng)前線程的threadlocals,即ThreadLocalMap ThreadLocalMap map = getMap(t); // 3.如果map不為null,則直接返回對(duì)應(yīng)的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 4.否則,則進(jìn)行初始化 return setInitialValue(); }
下面是setInitialValue
的代碼
private T setInitialValue() { //初始化屬性,其實(shí)就是null T value = initialValue(); //獲取當(dāng)前線程 Thread t = Thread.currentThread(); //通過(guò)當(dāng)前線程獲取ThreadLocalMap ThreadLocalMap map = getMap(t); //如果map不為null,則直接添加元素 if (map != null) { map.set(this, value); } else { //否則就創(chuàng)建,然后將創(chuàng)建好的map放入當(dāng)前線程的屬性threadlocals createMap(t, value); } //將當(dāng)前ThreadLocal實(shí)例注冊(cè)進(jìn)TerminatingThreadLocal類里面 if (this instanceof TerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this); } return value; }
這里我需要補(bǔ)充說(shuō)明一下TerminatingThreadLocal
。這個(gè)類是jdk11新出的,jdk8中并沒(méi)有這個(gè)類,所以在網(wǎng)上很多源碼分析中并未看見(jiàn)這個(gè)類的相關(guān)說(shuō)明。 這個(gè)類我看了一下源碼,其作用應(yīng)該是避免ThreadLocal內(nèi)存泄露的問(wèn)題(感興趣的可以去看看源碼,若有錯(cuò)誤,還請(qǐng)指正)。 這是官方對(duì)其的解釋:
/** * A thread-local variable that is notified when a thread terminates and * it has been initialized in the terminating thread (even if it was * initialized with a null value). * 一個(gè)線程局部變量, * 當(dāng)一個(gè)線程終止并且它已經(jīng)在終止線程中被初始化時(shí)被通知(即使它被初始化為一個(gè)空值)。 */
public void remove() { //如果當(dāng)前線程的threadLocals 變量不為空, 則刪除當(dāng)前線程中指定ThreadLocal 實(shí)例的本地變量。 ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); } }
在每個(gè)線程內(nèi)部都有一個(gè)名為threadLocals 的成員變量, 該變量的類型為Hash Map , 其中key 為我們定義的ThreadLocal 變量的this 引用, value 則為我們使用set 方法設(shè)置的值。每個(gè)線程的本地變量存放在線程自己的內(nèi)存變量threadLocals 中,如果當(dāng)前線程一直不消亡, 那么這些本地變量會(huì)一直存在, 所以可能會(huì)造成內(nèi)存溢出, 因此使用完畢后要記得調(diào)用ThreadLocal 的remove 方法刪除對(duì)應(yīng)線程的threadLocals 中的本地變量。
ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個(gè)ThreadLocal沒(méi)有外部強(qiáng)引用來(lái)引用它,那么系統(tǒng) GC 的時(shí)候,這個(gè)ThreadLocal勢(shì)必會(huì)被回收,這樣一來(lái),ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry,就沒(méi)有辦法訪問(wèn)這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠(yuǎn)無(wú)法回收,造成內(nèi)存泄漏。 其實(shí),ThreadLocalMap的設(shè)計(jì)中已經(jīng)考慮到這種情況,也加上了一些防護(hù)措施:在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)清除線程ThreadLocalMap里所有key為null的value。 但是這些被動(dòng)的預(yù)防措施并不能保證不會(huì)內(nèi)存泄漏:
使用static的ThreadLocal,延長(zhǎng)了ThreadLocal的生命周期,可能導(dǎo)致的內(nèi)存泄漏
分配使用了ThreadLocal又不再調(diào)用get(),set(),remove()方法,那么就會(huì)導(dǎo)致內(nèi)存泄漏
既然我們都知道,使用了弱引用會(huì)造成ThreadLocalMap內(nèi)存泄漏,那么官方為什么依然使用弱引用而不是強(qiáng)引用呢?這就要從使用弱引用和強(qiáng)引用的區(qū)別來(lái)說(shuō)起了:
如果使用強(qiáng)引用:我們知道,ThreadLocalMap的生命周期基本和Thread的生命周期一樣,當(dāng)前線程如果沒(méi)有終止,那么ThreadLocalMap始終不會(huì)被GC回收,而ThreadLocalMap持有對(duì)ThreadLocal的強(qiáng)引用,那么ThreadLocal也不會(huì)被回收,當(dāng)線程生命周期長(zhǎng),如果沒(méi)有手動(dòng)刪除,則會(huì)造成kv累積,從而導(dǎo)致OOM
如果使用弱引用:弱引用中的對(duì)象具有很短的聲明周期,因?yàn)樵谙到y(tǒng)GC時(shí),只要發(fā)現(xiàn)弱引用,不管堆空間是否足夠,都會(huì)將對(duì)象進(jìn)行回收。而當(dāng)ThreadLocal的強(qiáng)引用被回收時(shí),ThreadLocalMap所持有的弱引用也會(huì)被回收,如果沒(méi)有手動(dòng)刪除kv,那么會(huì)造成value累積,也會(huì)導(dǎo)致OOM
對(duì)比可知,使用弱引用至少可以保證不會(huì)因?yàn)閙ap的key累積從而導(dǎo)致OOM,而對(duì)應(yīng)的value可以通過(guò)remove,get,set方法在下一次調(diào)用時(shí)被清除??梢?jiàn),內(nèi)存泄露的根源不是弱引用,而是ThreadLocalMap的生命周期和Thread一樣長(zhǎng),造成累積導(dǎo)致的
既然問(wèn)題的根源是value的累積造成OOM,那么我們對(duì)癥下藥,每次使用完ThreadLocal調(diào)用remove()
方法清理掉就行了。
以上就是“Java ThreadLocal類如何使用”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。