Java中的ThreadLocal源碼及弱引用解析
引言
我們知道在通常情況下,對于主存中的變量,每一個線程都能夠訪問并修改該變量(或?qū)ο螅?/p>
與之相對的,如果我們需要實現(xiàn)每個線程擁有自己專屬的本地變量,該如何操作呢?
此時引出ThreadLocal類,通過ThreadLocal可以實現(xiàn)全局變量在多線程環(huán)境下的線程隔離。
每個線程都可以獨立地訪問和修改自己的全局變量副本,不會影響其他線程的副本。
這在某些場景下可以簡化代碼的編寫和理解。
源碼解析
示例代碼
我們先從一段簡單的代碼示例入手:
package Thread_; public class ThreadLocal { private static java.lang.ThreadLocal<Integer> counter = new java.lang.ThreadLocal<>(); public static void main(String[] args) { Runnable runnable = () -> { // 獲取當(dāng)前線程的計數(shù)器值,初始值為0 int count = counter.get() == null ? 0 : counter.get(); System.out.println(Thread.currentThread().getName() + " 的計數(shù)器值為: " + count); // 對計數(shù)器進行累加操作 counter.set(count + 1); // 模擬耗時操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 再次獲取計數(shù)器值 count = counter.get(); System.out.println(Thread.currentThread().getName() + " 的累加后計數(shù)器值為: " + count); }; // 創(chuàng)建三個線程并啟動 Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); Thread thread3 = new Thread(runnable); thread1.start(); thread2.start(); thread3.start(); } }
//結(jié)果如下:
Thread-1 的計數(shù)器值為: 0
Thread-0 的計數(shù)器值為: 0
Thread-2 的計數(shù)器值為: 0
Thread-1 的累加后計數(shù)器值為: 1
Thread-2 的累加后計數(shù)器值為: 1
Thread-0 的累加后計數(shù)器值為: 1
在代碼中,我們定義了一個ThreadLocal類的整形對象counter,用三個線程進行累加操作。
結(jié)果我們發(fā)現(xiàn),counter的值并沒有變?yōu)?,而是每個線程有一個自己的counter值,分別為1。
由此引出ThreadLoca的作用:
ThreadLocal對象在每個線程內(nèi)是共享的,在不同線程之間又是隔離的(每個線程都只能看到自己獨有的ThreadLocal對象的值),即,實現(xiàn)了線程范圍內(nèi)的局部變量的作用。
線程獨享原因
源碼解析
首先我們可以推測,如果要保證每個線程獨享一份數(shù)據(jù),那這份數(shù)據(jù)應(yīng)該要能夠從線程內(nèi)部進行引用。
實際上,線程的棧中存放了ThreadLocal.ThreadLocalMap這么一個屬性(初始化為空),ThreadLocalMap是ThreadLocal的一個靜態(tài)內(nèi)部類,以后數(shù)據(jù)就要以ThreadLocalMap的形式在堆中實例化,并讓threadLocals成為它的引用!這樣就完成了線程的獨享了。
public class Thread implements Runnable{ ... ... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ... }
我們回到示例中,注意:ThreadLocal<Integer> counter這個ThreadLocal對象的set方法,跟進源碼,詳細(xì)解釋如下:
// 對計數(shù)器進行累加操作 counter.set(count + 1); //源碼1. set源碼: public void set(T value) { Thread t = Thread.currentThread();//獲取當(dāng)前線程 ThreadLocalMap map = getMap(t);//讓map成為該線程內(nèi)的threadLocals所引用的堆中的對象! //見源碼2. //其實就是去嘗試引用實例化的ThreadLocalMap,但此時初始化為null,所以我們看下面的判斷: if (map != null) { map.set(this, value);//不為空,已經(jīng)有map,就把堆中的值賦值為counter,count + 1 } else { createMap(t, value);//為空,創(chuàng)建ThreadLocalMap的對象 //但要注意,createMap并不是讓map實例化,見下面源碼3. } } //源碼2. getMap源碼: ThreadLocalMap getMap(Thread t) { return t.threadLocals; //意思就是,返回這個t線程的ThreadLocal.ThreadLocalMap threadLocals = null里面這個threadLocals 對象 } //源碼3. createMap源碼: //傳入的this,其實就是counter。firstValue就是相應(yīng)的值count + 1。 //注意,是將t.threadLocals線程內(nèi)的這個ThreadLocalMap對象實例化,所以線程內(nèi)部的這個對象指向了堆中內(nèi)存的new...,實現(xiàn)了線程內(nèi)部的數(shù)據(jù)獨立。 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
概括:
看似是向ThreadLocal存入一個值,實際上是向當(dāng)前線程對象中的ThreadLocalMap對象存入值(如果為空,則實例化當(dāng)前線程對象中的ThreadLocalMap對象)ThreadLocalMap我們可以簡單的理解成一個Map,Map存的key就是ThreadLocal實例本身(counter),value是具體的值。
get方法同理,也是先取出當(dāng)前線程對象,再取出其指向的map里的值:
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(); }
1.3 ThreadLocalMap的key的弱引用
源碼如下:
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
通常ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應(yīng)key會導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal會被GC回收,不會內(nèi)存泄漏,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。
ThreadLocal中一個設(shè)計亮點是ThreadLocalMap中的Entry結(jié)構(gòu)的Key用到了弱引用。 試想如果使用強引用,如果ThreadLocalMap的Key使用強引用,那么Key對應(yīng)的ThreadLocal對象在沒有被外部引用時仍然無法被GC回收,因為Key存在于ThreadLocalMap中,而且線程是長時間存活的。這就可能導(dǎo)致ThreadLocal對象無法被回收,從而造成內(nèi)存泄漏。
使用了弱引用的話,JVM觸發(fā)GC回收弱引用后,ThreadLocalMap中會出現(xiàn)一些Key為null,但是Value不為null的Entry項,這些Entry項如果不主動清理,就會一直駐留在ThreadLocalMap中。此時,ThreadLocal在下一次調(diào)用get()、set()、remove()方法就可以刪除那些ThreadLocalMap中Key為null的值,起到了惰性刪除釋放內(nèi)存的作用。
到此這篇關(guān)于Java中的ThreadLocal源碼及弱引用解析的文章就介紹到這了,更多相關(guān)ThreadLocal源碼及弱引用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java多線程之ReentrantReadWriteLock源碼解析
這篇文章主要介紹了Java多線程之ReentrantReadWriteLock源碼解析,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-05-05詳解如何使用SpringBoot實現(xiàn)下載JSON文件
在?Spring?Boot?中實現(xiàn)文件下載功能,可以通過將?JSON?字符串作為文件內(nèi)容返回給客戶端從而實現(xiàn)JSON文件下載效果,下面我們就來看看具體操作吧2025-02-02postman?如何實現(xiàn)傳遞?ArrayList?給后臺
這篇文章主要介紹了postman?如何實現(xiàn)傳遞?ArrayList給后臺,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12Java wait和notifyAll實現(xiàn)簡單的阻塞隊列
這篇文章主要介紹了Java wait和notifyAll實現(xiàn)簡單的阻塞隊列,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10RocketMQ生產(chǎn)者調(diào)用start發(fā)送消息原理示例
這篇文章主要為大家介紹了RocketMQ生產(chǎn)者調(diào)用start發(fā)送消息原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11IDEA Maven Mybatis generator 自動生成代碼(實例講解)
下面小編就為大家分享一篇IDEA Maven Mybatis generator 自動生成代碼的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12