重新認(rèn)識(shí)Java中的ThreadLocal
說來也慚愧,這個(gè) ThreadLocal 其實(shí)一直都是一知半解,而且看了一下之后還發(fā)現(xiàn)記錯(cuò)了,所以還是記錄下
原先記憶里的都是反過來,一個(gè) ThreadLocal 是里面按照 thread 作為 key,存儲(chǔ)線程內(nèi)容的,真的是半解都米有,完全是錯(cuò)的,這樣就得用 concurrentHashMap 這種去存儲(chǔ)并且要鎖定線程了,然后內(nèi)容也只能存一個(gè)了,想想簡(jiǎn)直智障
究竟是啥結(jié)構(gòu)
比如我們?cè)诖a中 new 一個(gè) ThreadLocal,
public static void main(String[] args) { ThreadLocal<Man> tl = new ThreadLocal<>(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(tl.get()); }).start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } tl.set(new Man()); }).start(); } static class Man { String name = "nick"; }
這里構(gòu)造了兩個(gè)線程,一個(gè)先往里設(shè)值,一個(gè)后從里取,運(yùn)行看下結(jié)果,
知道這個(gè)用法的話肯定知道是取不到值的,只是具體的原理原來搞錯(cuò)了,我們來看下設(shè)值 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); }
寫博客這會(huì)我才明白我原來咋會(huì)錯(cuò)得這么離譜,看到第一行代碼 t 就是當(dāng)前線程,然后第二行就是用這個(gè)線程去getMap,然后我是把這個(gè)當(dāng)成從 map 里取值了,其實(shí)這里是
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
獲取 t 的 threadLocals 成員變量,那這個(gè) threadLocals 又是啥呢
它其實(shí)是線程 Thread 中的一個(gè)類型是java.lang.ThreadLocal.ThreadLocalMap的成員變量
這是 ThreadLocal 的一個(gè)靜態(tài)成員變量
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; } } }
全部代碼有點(diǎn)長(zhǎng),只截取了一小部分,然后我們?cè)倩仡^來分析前面說的 set 過程,再 copy 下代碼
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
獲取到 map 以后呢,如果 map 不為空,就往 map 里 set,這里注意 key 是啥,其實(shí)是當(dāng)前這個(gè) ThreadLocal,這里就比較明白了究竟是啥結(jié)構(gòu),每個(gè)線程都會(huì)維護(hù)自身的 ThreadLocalMap,它是線程的一個(gè)成員變量,當(dāng)創(chuàng)建 ThreadLocal 的時(shí)候,進(jìn)行設(shè)值的時(shí)候其實(shí)是往這個(gè) map 里以 ThreadLocal 作為 key,往里設(shè) value。
內(nèi)存泄漏是什么鬼
這里又要看下前面的 ThreadLocalMap 結(jié)構(gòu)了,類似 HashMap,它有個(gè) Entry 結(jié)構(gòu),在設(shè)置的時(shí)候會(huì)先包裝成一個(gè) Entry
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
這里其實(shí)比較重要的就是前面的 Entry 的構(gòu)造方法,Entry 是個(gè) WeakReference 的子類,然后在構(gòu)造方法里可以看到 key 會(huì)被包裝成一個(gè)弱引用,這里為什么使用弱引用,其實(shí)是方便這個(gè) key 被回收,如果前面的 ThreadLocal tl實(shí)例被設(shè)置成 null 了,如果這里是直接的強(qiáng)引用的話,就只能等到線程整個(gè)回收了,但是其實(shí)是弱引用也會(huì)有問題,主要是因?yàn)檫@個(gè) value,如果在 ThreadLocal tl 被設(shè)置成 null 了,那么其實(shí)這個(gè) value 就會(huì)沒法被訪問到,所以最好的操作還是在使用完了就 remove 掉
以上就是詳解Java中的ThreadLocal的詳細(xì)內(nèi)容,更多關(guān)于Java ThreadLocal的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的線程池代碼示例
線程池是管理線程的一個(gè)池子,通過阻塞隊(duì)列管理任務(wù),主要參數(shù)包括corePoolSize、maximumPoolSize、keepAliveTime等,這篇文章主要介紹了Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的線程池的相關(guān)資料,需要的朋友可以參考下2024-09-09JAVA中的靜態(tài)代理、動(dòng)態(tài)代理以及CGLIB動(dòng)態(tài)代理總結(jié)
本篇文章主要介紹了JAVA中的靜態(tài)代理、動(dòng)態(tài)代理以及CGLIB動(dòng)態(tài)代理總結(jié),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08Gitlab CI-CD自動(dòng)化部署SpringBoot項(xiàng)目的方法步驟
本文主要記錄如何通過Gitlab CI/CD自動(dòng)部署SpringBoot項(xiàng)目jar包。文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07Java中的FutureTask實(shí)現(xiàn)代碼實(shí)例
這篇文章主要介紹了Java中的FutureTask手寫代碼實(shí)例,FutureTask是Future的實(shí)現(xiàn),用來異步任務(wù)的獲取結(jié)果,可以啟動(dòng)和取消異步任務(wù),查詢異步任務(wù)是否計(jì)算結(jié)束以及獲取最終的異步任務(wù)的結(jié)果,需要的朋友可以參考下2023-12-12Java concurrency集合之ConcurrentLinkedQueue_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java concurrency集合之ConcurrentLinkedQueue,需要的朋友可以參考下2017-06-06SpringMVC使用@Valid注解進(jìn)行數(shù)據(jù)驗(yàn)證的方法
本篇文章主要介紹了SpringMVC使用@Valid注解進(jìn)行數(shù)據(jù)驗(yàn)證的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02Java設(shè)計(jì)模式之命令模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
命令模式就是對(duì)命令的封裝,下文中給大家介紹了命令模式類圖中的基本結(jié)構(gòu),對(duì)java設(shè)計(jì)模式之命令模式相關(guān)知識(shí)感興趣的朋友一起看看吧2017-08-08