中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

Java多線程:ThreadLocal

一、ThreadLocal基礎知識

  ThreadLocal是(shi)線程的一個(ge)本地化對(dui)象,或者說是(shi)局(ju)部變量。當工(gong)作于多(duo)線程(cheng)(cheng)中的(de)對象使用ThreadLocal維(wei)護變(bian)量時(shi),ThreadLocal為每(mei)個(ge)使用該(gai)變(bian)量的(de)線程(cheng)(cheng)分配一(yi)個(ge)獨(du)立(li)的(de)變(bian)量副本。所以(yi)每(mei)一(yi)個(ge)線程(cheng)(cheng)都可以(yi)獨(du)立(li)地改變(bian)自己的(de)副本,而不會影(ying)響其他線程(cheng)(cheng)所對應的(de)副本。

  ThreadLocal不是用來解決對象(xiang)共享訪問問題的(de),而主要是提供了線程(cheng)保持對象(xiang)的(de)方(fang)法和(he)避免(mian)參數傳遞的(de)方(fang)便(bian)的(de)對象(xiang)訪問方(fang)式(shi) 

  ThreadLocal的應(ying)用(yong)場合,最適合的是按線程(cheng)多實(shi)例(每個(ge)(ge)線程(cheng)對應(ying)一個(ge)(ge)實(shi)例)的對象的訪(fang)問,并且這(zhe)個(ge)(ge)對象很多地(di)方(fang)都要(yao)用(yong)到。

   概括起來(lai)說,對于(yu)多線(xian)程資源共享的問題,同(tong)(tong)步機制(zhi)synchronized采(cai)(cai)用(yong)了“以時(shi)(shi)間換空間”的方(fang)式,比(bi)如(ru)定義一(yi)個(ge)static變(bian)量,同(tong)(tong)步訪(fang)問,而ThreadLocal采(cai)(cai)用(yong)了“以空間換時(shi)(shi)間”的方(fang)式。前者僅提(ti)供(gong)(gong)一(yi)份(fen)變(bian)量,讓不(bu)同(tong)(tong)的線(xian)程排隊訪(fang)問,而后(hou)者為(wei)每一(yi)個(ge)線(xian)程都提(ti)供(gong)(gong)了一(yi)份(fen)變(bian)量,因(yin)此可以同(tong)(tong)時(shi)(shi)訪(fang)問而互(hu)不(bu)影響

 

二:如何存放線程本地變量?

       在ThreadLocal類中(zhong)有一個(ge)ThreadLocalMap, 用于存(cun)放每(mei)一個(ge)線程(cheng)的變量副(fu)本,Map中(zhong)元素的key為線程(cheng)對象(xiang),value為對應線程(cheng)的變量副(fu)本。

    另外,說ThreadLocal使(shi)得各線(xian)程能夠保持各自獨立的(de)(de)一(yi)(yi)個(ge)(ge)對(dui)(dui)(dui)象(xiang),并不是(shi)通過ThreadLocal.set()來實(shi)現的(de)(de),而是(shi)通過每個(ge)(ge)線(xian)程中的(de)(de)new 對(dui)(dui)(dui)象(xiang) 的(de)(de)操作來創建(jian)的(de)(de)對(dui)(dui)(dui)象(xiang),每個(ge)(ge)線(xian)程創建(jian)一(yi)(yi)個(ge)(ge),不是(shi)什么(me)對(dui)(dui)(dui)象(xiang)的(de)(de)拷貝或副本(ben)。通過ThreadLocal.set()將這個(ge)(ge)新(xin)創建(jian)的(de)(de)對(dui)(dui)(dui)象(xiang)的(de)(de)引用(yong)保存(cun)到各線(xian)程的(de)(de)自己的(de)(de)一(yi)(yi)個(ge)(ge)map中,每個(ge)(ge)線(xian)程都有這樣一(yi)(yi)個(ge)(ge)map,執行ThreadLocal.get()時,各線(xian)程從自己的(de)(de)map中取(qu)出(chu)放進去(qu)的(de)(de)對(dui)(dui)(dui)象(xiang),因此(ci)取(qu)出(chu)來的(de)(de)是(shi)各自自己線(xian)程中的(de)(de)對(dui)(dui)(dui)象(xiang),ThreadLocal實(shi)例(li)是(shi)作為map的(de)(de)key來使(shi)用(yong)的(de)(de)。 

  如果ThreadLocal.set()進去的(de)東(dong)西本來就是(shi)多(duo)個線(xian)程(cheng)共享的(de)同一個對象,那么多(duo)個線(xian)程(cheng)的(de)ThreadLocal.get()取得(de)的(de)還是(shi)這個共享對象本身(shen),還是(shi)有(you)并發訪問(wen)問(wen)題。

 

歸納了兩點: 
    1。每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。 
    2。將一個共用的ThreadLocal靜態實例作為key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然后在線程執行的各處通過這個靜態ThreadLocal實例的get()方法取得自己線程保存的那個對象,避免了將這個對象作為參數傳遞的麻煩。
 

 
    當然如果(guo)要(yao)把本來線程共享(xiang)的對象(xiang)通過ThreadLocal.set()放到線程中也可(ke)以(yi),可(ke)以(yi)實現避免參(can)數傳遞的訪(fang)問(wen)方式(shi),但是(shi)要(yao)注意get()到的是(shi)那同一個共享(xiang)對象(xiang),并發訪(fang)問(wen)問(wen)題要(yao)靠(kao)其他(ta)手段來解決。但一般來說線程共享(xiang)的對象(xiang)通過設置為某類的靜態變量就可(ke)以(yi)實現方便的訪(fang)問(wen)了,似乎沒必(bi)要(yao)放到線程中。 

    ThreadLocal的(de)(de)應用場合(he),我覺得最適(shi)合(he)的(de)(de)是按線(xian)程(cheng)多實例(每個(ge)線(xian)程(cheng)對應一個(ge)實例)的(de)(de)對象(xiang)(xiang)的(de)(de)訪問,并(bing)且這個(ge)對象(xiang)(xiang)很多地方(fang)都要用到。

三、源碼解讀

   很多(duo)人(ren)對ThreadLocal存在一定的誤解,說ThreadLocal中有(you)一個全(quan)局的Map,set時執行(xing)map.put(Thread.currentThread(), value),get和remove時也(ye)同理

首先看一下ThreadLocal的API:

  • get():返回此線程局部變量的當前線程副本中的值。
  • protected T initialValue(): 返回此線程局部變量的當前線程的“初始值”。
  • void remove(): 移除此線程局部變量當前線程的值。
  • void set(T value): 將此線程局部變量的當前線程副本中的值設置為指定值。 

   set方(fang)法:

/** 
 * Sets the current thread's copy of this thread-local variable 
 * to the specified value.  Most subclasses will have no need to 
 * override this method, relying solely on the {@link #initialValue} 
 * method to set the values of thread-locals. 
 * 
 * @param value the value to be stored in the current thread's copy of 
 *        this thread-local. 
 */  
public void set(T value) {  
    // 獲(huo)取當(dang)前線(xian)程對象  
    Thread t = Thread.currentThread();  
    // 獲取當(dang)前線程本地變量Map  
    ThreadLocalMap map = getMap(t);  
    // map不為空  
    if (map != null)  
        // 存值  
        map.set(this, value);  
    else  
        // 創建一個當(dang)前(qian)線程本地變量Map  
        createMap(t, value);  
}  
  
/** 
 * Get the map associated with a ThreadLocal. Overridden in 
 * InheritableThreadLocal. 
 * 
 * @param  t the current thread 
 * @return the map 
 */  
ThreadLocalMap getMap(Thread t) {  
    // 獲(huo)取當前線程的本(ben)地變量Map  
    return t.threadLocals;  
} 

 這(zhe)(zhe)(zhe)里(li)注意,ThreadLocal中(zhong)是(shi)(shi)有一(yi)個Map,但(dan)這(zhe)(zhe)(zhe)個Map不(bu)是(shi)(shi)我們平(ping)時使(shi)用(yong)的(de)(de)(de)(de)Map,而是(shi)(shi)ThreadLocalMap,ThreadLocalMap是(shi)(shi)ThreadLocal的(de)(de)(de)(de)一(yi)個內(nei)部類,不(bu)對(dui)外使(shi)用(yong)的(de)(de)(de)(de)。當使(shi)用(yong)ThreadLocal存(cun)值時,首先是(shi)(shi)獲取到(dao)當前(qian)線(xian)(xian)程(cheng)對(dui)象(xiang),然后獲取到(dao)當前(qian)線(xian)(xian)程(cheng)本地(di)變(bian)量(liang)Map,最后將當前(qian)使(shi)用(yong)的(de)(de)(de)(de)ThreadLocal和傳入的(de)(de)(de)(de)值放(fang)到(dao)Map中(zhong),也就是(shi)(shi)說ThreadLocalMap中(zhong)存(cun)的(de)(de)(de)(de)值是(shi)(shi)[ThreadLocal對(dui)象(xiang), 存(cun)放(fang)的(de)(de)(de)(de)值],這(zhe)(zhe)(zhe)樣做的(de)(de)(de)(de)好處(chu)是(shi)(shi),每(mei)個線(xian)(xian)程(cheng)都(dou)對(dui)應一(yi)個本地(di)變(bian)量(liang)的(de)(de)(de)(de)Map,所以一(yi)個線(xian)(xian)程(cheng)可(ke)以存(cun)在多個線(xian)(xian)程(cheng)本地(di)變(bian)量(liang)。

 


  get方(fang)法:

/** 
 * Returns the value in the current thread's copy of this 
 * thread-local variable.  If the variable has no value for the 
 * current thread, it is first initialized to the value returned 
 * by an invocation of the {@link #initialValue} method. 
 * 
 * @return the current thread's value of this thread-local 
 */  
public T get() {  
    Thread t = Thread.currentThread();  
    ThreadLocalMap map = getMap(t);  
    if (map != null) {  
        ThreadLocalMap.Entry e = map.getEntry(this);  
        if (e != null)  
            return (T)e.value;  
    }  
    // 如果值為(wei)空,則返(fan)回初始值  
    return setInitialValue();  
}  

看了之前set方法的分析,get方法也同理,需要說明的是,如(ru)果(guo)沒有進行過set操(cao)作,那從(cong)ThreadLocalMap中拿到(dao)的(de)值就是null

 四、使用場景

  ThreadLocal對象通常用于防止對(dui)可變的單實例變量或全局變量進行共享(xiang)

  當一個(ge)類中(zhong)使用了(le)static成(cheng)(cheng)員(yuan)變量(liang)的(de)時(shi)候,一定要(yao)(yao)多問問自己,這個(ge)static成(cheng)(cheng)員(yuan)變量(liang)需要(yao)(yao)考(kao)(kao)慮線程(cheng)安全(quan)嗎?也就(jiu)是說,多個(ge)線程(cheng)需要(yao)(yao)獨(du)享自己的(de)static成(cheng)(cheng)員(yuan)變量(liang)嗎?如果需要(yao)(yao)考(kao)(kao)慮,不妨使用ThreadLocal。

  ThreadLocal的(de)(de)主要應用(yong)(yong)場景(jing)為(wei)多線程(cheng)多實(shi)例(每個(ge)(ge)(ge)線程(cheng)對應一(yi)個(ge)(ge)(ge)實(shi)例)的(de)(de)對象(xiang)的(de)(de)訪(fang)問,并(bing)且(qie)這個(ge)(ge)(ge)對象(xiang)很多地方都要用(yong)(yong)到。例如(ru):同(tong)一(yi)個(ge)(ge)(ge)網(wang)站登錄用(yong)(yong)戶(hu),每個(ge)(ge)(ge)用(yong)(yong)戶(hu)服(fu)務器會(hui)為(wei)其開一(yi)個(ge)(ge)(ge)線程(cheng),每個(ge)(ge)(ge)線程(cheng)中創建一(yi)個(ge)(ge)(ge)ThreadLocal,里(li)面存用(yong)(yong)戶(hu)基本信息等,在很多頁面跳轉時,會(hui)顯示用(yong)(yong)戶(hu)信息或者(zhe)得(de)到用(yong)(yong)戶(hu)的(de)(de)一(yi)些信息等頻繁(fan)操作,這樣多線程(cheng)之間并(bing)沒有聯系而(er)且(qie)當前線程(cheng)也可以及(ji)時獲取(qu)想要的(de)(de)數據。

  

五、注意事項

  

  ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用來引用它,那么系統 GC 的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現keynullEntry,就沒有辦法訪問這些keynullEntryvalue,如果當前線程再遲遲不結束的話,這些keynullEntryvalue就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內存泄漏。

其實,ThreadLocalMap的設計中已經考慮到這種情況,也加上了一些防護措施:在ThreadLocalget(),set(),remove()的時候都會清除線程ThreadLocalMap里所有keynullvalue

但(dan)是這(zhe)些被動的預(yu)防措施并不能(neng)保證不會內(nei)存泄(xie)漏:

  • 使用staticThreadLocal,延長了ThreadLocal的生命周期,可能導致的內存泄漏(參考)。
  • 分配使用了ThreadLocal又不再調用get(),set(),remove()方法,那么就會導致內存泄漏。

為什么使用(yong)弱引用(yong)

從表面上看內存泄漏的根源在于使用了弱引用。網上的文章大多著重分析ThreadLocal使用了弱引用會導致內存泄漏,但是另一個問題也同樣值得思考:為什么使用弱引用而不是強引用?

我們先來看(kan)看(kan)官(guan)方文檔的說法:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
為了應對非常大和長時間的用(yong)途(tu),哈希表使用(yong)弱引用(yong)的 key。

下面我們分兩(liang)種情況討論(lun):

  • key 使用強引用:引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏。
  • key 使用弱引用:引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value在下一次ThreadLocalMap調用set,getremove的時候會被清除。

比較兩種情況,我們可以發現:由于ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動刪除對應key,都會導致內存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除

因此,ThreadLocal內存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應key就會導致內存泄漏,而不是因為弱引用。

 

綜合上面的分析,我們可以理解ThreadLocal內存泄漏的前因后果,那么怎么避免內存泄漏呢? 

  ①  使(shi)用結束以(yi)后進行remove操作,避免ThreadLocal對象越(yue)來越(yue)大。
  ②  高(gao)并發的場景:由于ThreadLocal內(nei)部使用(yong)HashMap的原理,key=currentThread,因為HashMap是非線(xian)程安全的,一定要注意hashmap.resize的時候,可能(neng)會(hui)導致某幾個(ge)CPU 100%的問題(ti),進而導致應用(yong)出現資(zi)源耗盡等不可預知的問題(ti)。

在使用線程池的情況下,沒有及時清理ThreadLocal,不僅是內存泄漏的問題,更嚴重的是可能導致業務邏輯出現問題。所以,使用ThreadLocal就跟加鎖完要解鎖一樣,用完就清理。

 

參考資料(liao):

    ThreadLocal

posted @ 2014-09-08 09:23  ^_TONY_^  閱讀(2340)  評論(2)    收藏  舉報