Java線程中的ThreadLocal類解讀
Java的ThreadLocal類
ThreadLocal是一個泛型類,作用是實現(xiàn)線程隔離,ThreadLocal類型的變量,在每個線程中都會對應(yīng)一個具體對象,對象類型需要在聲明ThreadLocal變量時指定。
ThreadLocal的使用示例
package org.example.thread; import org.example.domain.Book; public class ThreadLocalTest { ThreadLocal<Book> localBook = new ThreadLocal<>(); Book book = new Book(); public void test() { Thread thread = new Thread(new Runnable() { @Override public void run() { localBook.set(book); System.out.println(localBook.get()); localBook.remove(); } }); thread.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(localBook.get()); localBook.remove(); } public static void main(String[] arg) { ThreadLocalTest localBookTest = new ThreadLocalTest(); localBookTest.test(); } }
運行結(jié)果如下:
示例代碼中,只聲明了一個 ThreadLocal 變量localBook,在子線程中設(shè)置了localBook的值,但是在最后主線程中進行打印時,發(fā)現(xiàn)為null,和子線程中的結(jié)果不一樣。
具體原因還要看看ThreadLocal類,以及內(nèi)部 set、get等方法的實現(xiàn)。
原理
1、ThreadLocal中有一個靜態(tài)內(nèi)部類 ThreadLocalMap,ThreadLocalMap中維護了一個 Entry數(shù)組,Entry又是ThreadLocalMap的內(nèi)部類,用來表示一個KV鍵值對。部分源碼如下:
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; private Entry[] table;
Entry的 key是一個指向ThreadLocal對象的弱引用,value則指向與key對應(yīng)的Object對象。
2、Thread類中,有一個ThreadLocalMap類型的成員變量,初始為null:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
也就是說,每個線程對象中,都有一個ThreadLocalMap,它里面可以有很多個Entry,每個Entry中的key都指向一個ThreadLocal對象,value則指向與key相對應(yīng)的Object對象。在線程中調(diào)用ThreadLocal對象的set、get方法,實際操作的是當(dāng)前線程自己的ThreadLocalMap。
3、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); }
果然,這里先拿到當(dāng)前線程中的ThreadLocalMap,如果不為空則直接添加一組鍵值對,key是當(dāng)前的ThreadLocal變量; 如果map為空,則新建一個map,同樣添加一組鍵值對,key是當(dāng)前的ThreadLocal變量,然后將map賦值給當(dāng)前線程的ThreadLocalMap。
4、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(); }
同理,get方法拿到的也是線程本地的ThreadLocalMap中,與當(dāng)前ThreadLocal變量對應(yīng)的value對象。
5、remove方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); } } private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
remove方法,移除當(dāng)前線程的ThreadLocalMap中,與當(dāng)前ThreadLocal對象對應(yīng)的鍵值對(key、value及Entry置空)。
內(nèi)存泄漏相關(guān)
1、為什么ThreadLocalMap中的key要使用弱引用?
弱引用特性:在對象沒有被強引用指向,而僅被弱引用指向的情況下,發(fā)生垃圾回收時,會直接清理掉該對象。 套用網(wǎng)上出現(xiàn)較多的一張圖片來表示ThreadLocal變量的內(nèi)存結(jié)構(gòu):
這里實線表示強引用,虛線表示弱引用。 假設(shè)key也是強引用,如果我們把 ThreadLocal Ref這個引用置為null,表示我們不再需要這個ThreadLocal對象,那么發(fā)生垃圾回收時,這個變量理應(yīng)被回收掉;但實際并非如此,因為當(dāng)前線程中還持有一個強引用,也就是ThreadLocalMap中的key還在指向它,那么只要線程不結(jié)束,這個沒有實際作用的ThreadLocal對象就一直不會被回收,從而出現(xiàn)內(nèi)存泄漏。而將key設(shè)計為弱引用,就能保證這種情況下ThreadLocal對象被回收,一定程度避免內(nèi)存泄漏問題。
2、為什么使用完ThreadLocal對象,要在線程中調(diào)用remove方法? 雖然弱引用可以使不再使用的ThreadLocal對象被回收掉,但還有一個問題: Entry中的key被置為了null,對應(yīng)的value已經(jīng)無法通過key訪問到,然而Current thread -> ThreadLocalMap -> Entry(value) -> my value 這條強引用鏈仍然存在,也就是說還存在key為null,但value不為null的entry,如果這類entry不被清理掉,還是會導(dǎo)致內(nèi)存泄露。
key為null的entry如何清理? 在ThreadLocalMap的Entry數(shù)組中,key為null的entry也會占用一個數(shù)組下標,而Threadlocal的set、get等方法,正是根據(jù)key的hashCode計算得到數(shù)組下標,然后根據(jù)下標找到對應(yīng)的entry進行操作。 為了維護ThreadLocalMap的可用性,不讓這些key為null的無用entry占用過多空間,set、get方法在某些情況下也會對key為null的entry進行清理。但是用戶有可能不再需要調(diào)用其他ThreadLocal變量的set或get方法,所以這種方式是被動且沒有針對性的。
建議在不使用ThreadLocal對象之后,直接調(diào)用remove方法,其作用就是將對應(yīng)entry的key、value置空,并將entry從數(shù)組中移除,切斷下圖中的這三處引用關(guān)系,防止內(nèi)存泄漏。
到此這篇關(guān)于Java線程中的ThreadLocal類解讀的文章就介紹到這了,更多相關(guān)Java的ThreadLocal類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ThreadLocal線程在Java框架中的應(yīng)用及原理深入理解
這篇文章主要介紹了ThreadLocal在Java框架中的應(yīng)用及原理深入理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01spring cloud gateway集成hystrix實戰(zhàn)篇
這篇文章主要介紹了spring cloud gateway集成hystrix實戰(zhàn),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Java中的Set、List、Map的用法與區(qū)別介紹
這篇文章主要介紹了Java中的Set、List、Map的用法與區(qū)別,需要的朋友可以參考下2016-06-06