Java中ThreadLocal的作用有哪些

這篇文章主要介紹“Java中ThreadLocal的作用有哪些”,在日常操作中,相信很多人在Java中ThreadLocal的作用有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中ThreadLocal的作用有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

目前創(chuàng)新互聯(lián)公司已為近千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬主機、網(wǎng)站托管、服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計、三原網(wǎng)站維護等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

開場白

張三最近天氣很熱心情不是很好,所以他決定出去面試跟面試官聊聊天排解一下,結(jié)果剛投遞簡歷就有人約了面試。

我丟,什么情況怎么剛投遞出去就有人約我面試了?誒。。。真煩啊,哥已經(jīng)不在江湖這么久了,江湖還是有哥的傳說,我還是這么搶手的么?太煩惱了,帥無罪。

暗自竊喜的張三來到了某東現(xiàn)場面試的辦公室,我丟,這面試官?不是吧,這滿是劃痕的Mac,這發(fā)量,難道就是傳說中的架構(gòu)師?

張三的心態(tài)一下子就崩了,出來第一場面試就遇到一個頂級面試官,這誰頂?shù)米“ ?/p>

你好,我是你的面試官Tony,看我的發(fā)型應(yīng)該你能猜到我的身份了,我也話不說,我們直接開始好不好?看你簡歷寫了多線程,來你跟我聊一下ThreadLocal吧,我很久沒寫代碼不太熟悉了,你幫我回憶一下。

我丟?這TM是人話?這是什么邏輯啊,說是問多線程然后一上來就來個這么冷門的ThreadLocal?心態(tài)崩了呀,再說你TM自己忘了不知道下去看看書么,來我這里找答案是什么鬼啊...

盡管十分不情愿,但是張三還是高速運轉(zhuǎn)他的小腦袋,回憶起了ThreadLocal的種種細節(jié)...

面試官說實話我在實際開發(fā)過程中用到ThreadLocal的地方不是很多,我在寫這個文章的時候還刻意去把我電腦上幾十個項目打開之后去全局搜索ThreadLocal發(fā)現(xiàn)除了系統(tǒng)源碼的使用,很少在項目中用到,不過也還是有的。

Java中ThreadLocal的作用有哪些

ThreadLocal的作用主要是做數(shù)據(jù)隔離,填充的數(shù)據(jù)只屬于當前線程,變量的數(shù)據(jù)對別的線程而言是相對隔離的,在多線程環(huán)境下,如何防止自己的變量被其它線程篡改。

你能跟我說說它隔離有什么用,會用在什么場景么?這,我都說了我很少用了,還問我,難受了呀,哦哦哦,有了想起來了,事務(wù)隔離級別。

面試官你好,其實我第一時間想到的就是Spring實現(xiàn)事務(wù)隔離級別的源碼,這還是當時我大學(xué)被女朋友甩了,一個人在圖書館哭泣的時候無意間發(fā)現(xiàn)的。

Spring采用Threadlocal的方式,來保證單個線程中的數(shù)據(jù)庫操作使用的是同一個數(shù)據(jù)庫連接,同時,采用這種方式可以使業(yè)務(wù)層使用事務(wù)時不需要感知并管理connection對象,通過傳播級別,巧妙地管理多個事務(wù)配置之間的切換,掛起和恢復(fù)。

Spring框架里面就是用的ThreadLocal來實現(xiàn)這種隔離,主要是在TransactionSynchronizationManager這個類里面,代碼如下所示:

private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);   private static final ThreadLocal<Map<Object, Object>> resources =    new NamedThreadLocal<>("Transactional resources");   private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =    new NamedThreadLocal<>("Transaction synchronizations");   private static final ThreadLocal<String> currentTransactionName =    new NamedThreadLocal<>("Current transaction name");    &hellip;&hellip;

Spring的事務(wù)主要是ThreadLocal和AOP去做實現(xiàn)的,我這里提一下,大家知道每個線程自己的鏈接是靠ThreadLocal保存的就好了,繼續(xù)的細節(jié)我會在Spring章節(jié)細說的,暖么?

除了源碼里面使用到ThreadLocal的場景,你自己有使用他的場景么?一般你會怎么用呢?

來了來了,加分項來了,這個我還真遇到過,裝B的機會終于來了。

有的有的面試官,這個我會!!!

之前我們上線后發(fā)現(xiàn)部分用戶的日期居然不對了,排查下來是SimpleDataFormat的鍋,當時我們使用SimpleDataFormat的parse()方法,內(nèi)部有一個Calendar對象,調(diào)用SimpleDataFormat的parse()方法會先調(diào)用Calendar.clear(),然后調(diào)用Calendar.add(),如果一個線程先調(diào)用了add()然后另一個線程又調(diào)用了clear(),這時候parse()方法解析的時間就不對了。

其實要解決這個問題很簡單,讓每個線程都new 一個自己的  SimpleDataFormat就好了,但是1000個線程難道new1000個SimpleDataFormat?

所以當時我們使用了線程池加上ThreadLocal包裝SimpleDataFormat,再調(diào)用initialValue讓每個線程有一個SimpleDataFormat的副本,從而解決了線程安全的問題,也提高了性能。

那&hellip;&hellip;還有還有,我還有,您別著急問下一個,讓我再加點分,拖延一下面試時間。

我在項目中存在一個線程經(jīng)常遇到橫跨若干方法調(diào)用,需要傳遞的對象,也就是上下文(Context),它是一種狀態(tài),經(jīng)常就是是用戶身份、任務(wù)信息等,就會存在過渡傳參的問題。

使用到類似責(zé)任鏈模式,給每個方法增加一個context參數(shù)非常麻煩,而且有些時候,如果調(diào)用鏈有無法修改源碼的第三方庫,對象參數(shù)就傳不進去了,所以我使用到了ThreadLocal去做了一下改造,這樣只需要在調(diào)用前在ThreadLocal中設(shè)置參數(shù),其他地方get一下就好了。

before    void work(User user) {     getInfo(user);     checkInfo(user);     setSomeThing(user);     log(user); }  then    void work(User user) { try{    threadLocalUser.set(user);    // 他們內(nèi)部  User u = threadLocalUser.get(); 就好了     getInfo();     checkInfo();     setSomeThing();     log();     } finally {      threadLocalUser.remove();     } }

我看了一下很多場景的cookie,session等數(shù)據(jù)隔離都是通過ThreadLocal去做實現(xiàn)的。

對了我面試官允許我再秀一下知識廣度,在Android中,Looper類就是利用了ThreadLocal的特性,保證每個線程只存在一個Looper對象。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static void prepare(boolean quitAllowed) {     if (sThreadLocal.get() != null) {         throw new RuntimeException("Only one Looper may be created per thread");     }     sThreadLocal.set(new Looper(quitAllowed)); }

面試官:我丟,這貨怎么知道這么多場景?還把Android都扯了出來,不是吧阿sir,下面我要考考他原理了。

嗯嗯,你回答得很好,那你能跟我說說他底層實現(xiàn)的原理么?

好的面試官,我先說一下他的使用:

ThreadLocal<String> localName = new ThreadLocal(); localName.set("張三"); String name = localName.get(); localName.remove();

其實使用真的很簡單,線程進來之后初始化一個可以泛型的ThreadLocal對象,之后這個線程只要在remove之前去get,都能拿到之前set的值,注意這里我說的是remove之前。

他是能做到線程間數(shù)據(jù)隔離的,所以別的線程使用get()方法是沒辦法拿到其他線程的值的,但是有辦法可以做到,我后面會說。

我們先看看他set的源碼:

public void set(T value) {     Thread t = Thread.currentThread();// 獲取當前線程     ThreadLocalMap map = getMap(t);// 獲取ThreadLocalMap對象     if (map != null) // 校驗對象是否為空         map.set(this, value); // 不為空set     else         createMap(t, value); // 為空創(chuàng)建一個map對象 }

大家可以發(fā)現(xiàn)set的源碼很簡單,主要就是ThreadLocalMap我們需要關(guān)注一下,而ThreadLocalMap呢是當前線程Thread一個叫threadLocals的變量中獲取的。

ThreadLocalMap getMap(Thread t) {         return t.threadLocals;     } public class Thread implements Runnable {       &hellip;&hellip;      /* ThreadLocal values pertaining to this thread. This map is maintained      * by the ThreadLocal class. */     ThreadLocal.ThreadLocalMap threadLocals = null;      /*      * InheritableThreadLocal values pertaining to this thread. This map is      * maintained by the InheritableThreadLocal class.      */     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;         &hellip;&hellip;

這里我們基本上可以找到ThreadLocal數(shù)據(jù)隔離的真相了,每個線程Thread都維護了自己的threadLocals變量,所以在每個線程創(chuàng)建ThreadLocal的時候,實際上數(shù)據(jù)是存在自己線程Thread的threadLocals變量里面的,別人沒辦法拿到,從而實現(xiàn)了隔離。

ThreadLocalMap底層結(jié)構(gòu)是怎么樣子的呢?

面試官這個問題問得好啊,內(nèi)心暗罵,讓我歇一會不行么?

張三笑著回答道,既然有個Map那他的數(shù)據(jù)結(jié)構(gòu)其實是很像HashMap的,但是看源碼可以發(fā)現(xiàn),它并未實現(xiàn)Map接口,而且他的Entry是繼承WeakReference(弱引用)的,也沒有看到HashMap中的next,所以不存在鏈表了。

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;             }         }         &hellip;&hellip;     }

結(jié)構(gòu)大概長這樣:

Java中ThreadLocal的作用有哪些

稍等,我有兩個疑問你可以解答一下么?

好呀,面試官你說。

為什么需要數(shù)組呢?沒有了鏈表怎么解決Hash沖突呢?

用數(shù)組是因為,我們開發(fā)過程中可以一個線程可以有多個TreadLocal來存放不同類型的對象的,但是他們都將放到你當前線程的ThreadLocalMap里,所以肯定要數(shù)組來存。

至于Hash沖突,我們先看一下源碼:

private void set(ThreadLocal<?> key, Object value) {            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();         }

我從源碼里面看到ThreadLocalMap在存儲的時候會給每一個ThreadLocal對象一個threadLocalHashCode,在插入過程中,根據(jù)ThreadLocal對象的hash值,定位到table中的位置i,int  i = key.threadLocalHashCode & (len-1)。

然后會判斷一下:如果當前位置是空的,就初始化一個Entry對象放在位置i上;

if (k == null) {     replaceStaleEntry(key, value, i);     return; }

如果位置i不為空,如果這個Entry對象的key正好是即將設(shè)置的key,那么就刷新Entry中的value;

if (k == key) {     e.value = value;     return; }

如果位置i的不為空,而且key不等于entry,那就找下一個空位置,直到為空為止。

Java中ThreadLocal的作用有哪些

這樣的話,在get的時候,也會根據(jù)ThreadLocal對象的hash值,定位到table中的位置,然后判斷該位置Entry對象中的key是否和get的key一致,如果不一致,就判斷下一個位置,set和get如果沖突嚴重的話,效率還是很低的。

以下是get的源碼,是不是就感覺很好懂了:

private Entry getEntry(ThreadLocal<?> key) {            int i = key.threadLocalHashCode & (table.length - 1);            Entry e = table[i];            if (e != null && e.get() == key)                return e;            else                return getEntryAfterMiss(key, i, e);        }  private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {            Entry[] tab = table;            int len = tab.length; / get的時候一樣是根據(jù)ThreadLocal獲取到table的i值,然后查找數(shù)據(jù)拿到后會對比key是否相等  if (e != null && e.get() == key)。            while (e != null) {                ThreadLocal<?> k = e.get();              // 相等就直接返回,不相等就繼續(xù)查找,找到相等位置。                if (k == key)                    return e;                if (k == null)                    expungeStaleEntry(i);                else                    i = nextIndex(i, len);                e = tab[i];            }            return null;        }

能跟我說一下對象存放在哪里么?

在Java中,棧內(nèi)存歸屬于單個線程,每個線程都會有一個棧內(nèi)存,其存儲的變量只能在其所屬線程中可見,即棧內(nèi)存可以理解成線程的私有內(nèi)存,而堆內(nèi)存中的對象對所有線程可見,堆內(nèi)存中的對象可以被所有線程訪問。

那么是不是說ThreadLocal的實例以及其值存放在棧上呢?

其實不是的,因為ThreadLocal實例實際上也是被其創(chuàng)建的類持有(更頂端應(yīng)該是被線程持有),而ThreadLocal的值其實也是被線程實例持有,它們都是位于堆上,只是通過一些技巧將可見性修改成了線程可見。

如果我想共享線程的ThreadLocal數(shù)據(jù)怎么辦?

使用InheritableThreadLocal可以實現(xiàn)多個線程訪問ThreadLocal的值,我們在主線程中創(chuàng)建一個InheritableThreadLocal的實例,然后在子線程中得到這個InheritableThreadLocal實例設(shè)置的值。

private void test() {     final ThreadLocal threadLocal = new InheritableThreadLocal();        threadLocal.set("帥得一匹");     Thread t = new Thread() {             @Override             public void run() {                   super.run();                   Log.i( "張三帥么 =" + threadLocal.get());             }       };             t.start();  }

在子線程中我是能夠正常輸出那一行日志的,這也是我之前面試視頻提到過的父子線程數(shù)據(jù)傳遞的問題。

怎么傳遞的呀?

傳遞的邏輯很簡單,我在開頭Thread代碼提到threadLocals的時候,你們再往下看看我刻意放了另外一個變量:

Java中ThreadLocal的作用有哪些

Thread源碼中,我們看看Thread.init初始化創(chuàng)建的時候做了什么:

public class Thread implements Runnable {   &hellip;&hellip;    if (inheritThreadLocals && parent.inheritableThreadLocals != null)       this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);   &hellip;&hellip; }

我就截取了部分代碼,如果線程的inheritThreadLocals變量不為空,比如我們上面的例子,而且父線程的inheritThreadLocals也存在,那么我就把父線程的inheritThreadLocals給當前線程的inheritThreadLocals。

是不是很有意思?

小伙子你懂的確實很多,那你算是一個深度的ThreadLocal用戶了,你發(fā)現(xiàn)ThreadLocal的問題了么?

你是說內(nèi)存泄露么?

我丟,這小子為啥知道我要問什么?嗯嗯對的,你說一下。

這個問題確實會存在的,我跟大家說一下為什么,還記得我上面的代碼么?

Java中ThreadLocal的作用有哪些

ThreadLocal在保存的時候會把自己當做Key存在ThreadLocalMap中,正常情況應(yīng)該是key和value都應(yīng)該被外界強引用才對,但是現(xiàn)在key被設(shè)計成WeakReference弱引用了。

Java中ThreadLocal的作用有哪些

我先給大家介紹一下弱引用:

只具有弱引用的對象擁有更短暫的生命周期,在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。

不過,由于垃圾回收器是一個優(yōu)先級很低的線程,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象。

這就導(dǎo)致了一個問題,ThreadLocal在沒有外部強引用時,發(fā)生GC時會被回收,如果創(chuàng)建ThreadLocal的線程一直持續(xù)運行,那么這個Entry對象中的value就有可能一直得不到回收,發(fā)生內(nèi)存泄露。

就比如線程池里面的線程,線程都是復(fù)用的,那么之前的線程實例處理完之后,出于復(fù)用的目的線程依然存活,所以,ThreadLocal設(shè)定的value值被持有,導(dǎo)致內(nèi)存泄露。

按照道理一個線程使用完,ThreadLocalMap是應(yīng)該要被清空的,但是現(xiàn)在線程被復(fù)用了。

那怎么解決?

在代碼的最后使用remove就好了,我們只要記得在使用的最后用remove把值清空就好了。

ThreadLocallocalName = new ThreadLocal();try { localName.set("張三");  &hellip;&hellip;} finally { localName.remove();}

remove的源碼很簡單,找到對應(yīng)的值全部置空,這樣在垃圾回收器回收的時候,會自動把他們回收掉。

那為什么ThreadLocalMap的key要設(shè)計成弱引用?key不設(shè)置成弱引用的話就會造成和entry中value一樣內(nèi)存泄漏的場景。

到此,關(guān)于“Java中ThreadLocal的作用有哪些”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

分享文章:Java中ThreadLocal的作用有哪些
文章地址:http://bm7419.com/article0/psspio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營銷、營銷型網(wǎng)站建設(shè)、定制開發(fā)、網(wǎng)站設(shè)計公司、商城網(wǎng)站、網(wǎng)站收錄

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化