1. 程式人生 > >並發編程(四):ThreadLocal從源碼分析總結到內存泄漏

並發編程(四):ThreadLocal從源碼分析總結到內存泄漏

ngs 一個 交互 而且 當前 logs 點雲 然而 垃圾

一、目錄

1、ThreadLocal是什麽?有什麽用? 2、ThreadLocal源碼簡要總結? 3、ThreadLocal為什麽會導致內存泄漏?

二、ThreadLocal是什麽?有什麽用?

引入話題:在並發條件下,如何正確獲得共享數據?舉例:假設有多個用戶需要獲取用戶信息,一個線程對應一個用戶。在mybatis中,session用於操作數據庫,那麽設置、獲取操作分別是session.set()、session.get(),如何保證每個線程都能正確操作達到想要的結果?
/**
 * 回顧synchronized在多線程共享線程的問題
 * 
@author qiuyongAaron */ public class ThreadLocalOne { volatile Person person=new Person(); public synchronized String setAndGet(String name){ //System.out.print(Thread.currentThread().getName()+":"); person.name=name; //模擬網絡延遲 try { TimeUnit.SECONDS.sleep(
2); } catch (InterruptedException e) { e.printStackTrace(); } return person.name; } public static void main(String[] args) { ThreadLocalOne threadLocal=new ThreadLocalOne(); new Thread(()->System.out.println(threadLocal.setAndGet("arron")),"t1").start();
new Thread(()->System.out.println(threadLocal.setAndGet("tony")),"t2").start(); } } class Person{ String name="tom"; public Person(String name) { this.name=name; } public Person(){} } 運行結果: 無synchronized: t1:tony t2:tony 有synchronized: t1:arron t2:tony
步驟分析:
  1. 無synchronized的時候,因為非原子操作,顯然不是預想結果,可參考我關於synchronized的討論。
  2. 現在,我們的需求是:每個線程獨立的設置獲取person信息,不被線程打擾。
  3. 因為,person是共享數據,用同步互斥鎖synchronized,當一個線程訪問共享數據的時候,其他線程堵塞,不再多余贅述。
通過舉例問題,可能大家又會很疑惑? mybatis、hibernate是如何實現的呢? synchronized不會很消耗資源,當成千上萬個操作的時候,承受並發不說,數據返回延遲如何確保用戶體驗? ThreadLocal是什麽?有什麽用?
/**
 * 談談ThreadLocal的作用
 * @author qiuyongAaron
 */
public class ThreadLocalThree {
     ThreadLocal<Person> threadLocal=new ThreadLocal<Person>();
     public String setAndGet(String name){
           threadLocal.set(new Person(name));
           try {
                TimeUnit.SECONDS.sleep(2);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
           return threadLocal.get().name;
     }
 
     public static void main(String[] args) {
           ThreadLocalThree  threadLocal=new ThreadLocalThree();
           new Thread(()->System.out.println("t1:"+threadLocal.setAndGet("arron")),"t1").start();
           new Thread(()->System.out.println("t2:"+threadLocal.setAndGet("tony")),"t2").start();
     }
}
運行結果:
t1:arron
t2:tony
分析: 1、根據預期結果,那ThreadLocal到底是什麽? 回顧Java內存模型:   技術分享 技術分享 在虛擬機中,堆內存用於存儲共享數據(實例對象),堆內存也就是這裏說的主內存。 每個線程將會在堆內存中開辟一塊空間叫做線程的工作內存,附帶一塊緩存區用於存儲共享數據副本。那麽,共享數據在堆內存當中,線程通信就是通過主內存為中介,線程在本地內存讀並且操作完共享變量操作完畢以後,把值寫入主內存。
  1. ThreadLocal被稱為線程局部變量,說白了,他就是線程工作內存的一小塊內存,用於存儲數據。
  2. 那麽,ThreadLocal.set()、ThreadLocal.get()方法,就相當於把數據存儲於線程本地,取也是在本地內存讀取。就不會像synchronized需要頻繁的修改主內存的數據,再把數據復制到工作內存,也大大提高訪問效率。
2、ThreadLocal到底有什麽用?
  1. 回到最開始的舉例,也就等價於mabatis、hibernate為什麽要使用threadlocal來存儲session?
  2. 作用一:因為線程間的數據交互是通過工作內存與主存的頻繁讀寫完成通信,然而存儲於線程本地內存,提高訪問效率,避免線程阻塞造成cpu吞吐率下降。
  3. 作用二:在多線程中,每一個線程都需要維護session,輕易完成對線程獨享資源的操作。
總結: Threadlocal是什麽?在堆內存中,每個線程對應一塊工作內存,threadlocal就是工作內存的一小塊內存。 Threadlocal有什麽用?threadlocal用於存取線程獨享數據,提高訪問效率。

三、ThreadLocal源碼簡要總結?

那有同學可能還是有點雲裏霧裏,感覺還是沒有吃透?那線程內部如何去保證線程獨享數據呢? 在這裏,我只做簡要總結,若有興趣,可參考文章尾部的文章鏈接。重點看get、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);
 }
分析:
  1. 一個線程對應一個ThreadLocalMap ,可以存儲多個ThreadLocal對象。
  2. ThreadLocal對象作為key、獨享數據作為value。
  3. ThreadLocalMap可參考HashMap,在ThreadMap裏面存在Entry數組也就是一個Entry一個鍵值對。
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. 一個線程對應一個ThreadLocalMap,get()就是當前線程獲取自己的ThreadLocalMap。
  2. 線程根據使用那一小塊的threadlocal,根據ThreadLocal對象作為key,去獲取存儲於ThreadLocalMap中的值。
總結: 回顧一下,我們在單線程中如何使用HashMap的?hashMap根據數組+鏈表來實現HashMap,一個key對應一個value。那麽,我們抽象一下,Threadlocal也相當於在多線程中的一種HashMap用法,相當於對ThradLocal的操作也就如單線程操作一樣。 總之,ThreadLocal就是堆內存的一塊小內存,它用ThreadLocalMap維護ThreadLocal對象作為key,獨享數據作為value的東西。

四、ThreadLocal為什麽會導致內存泄漏?

synchronized是用時間換空間、ThreadLocal是用空間換時間,為什麽這麽說? 因為synchronized操作數據,只需要在主存存一個變量即可,就阻塞等共享變量,而ThreadLocal是每個線程都創建一塊小的堆工作內存。顯然,印證了上面的說法。 一個線程對應一塊工作內存,線程可以存儲多個ThreadLocal。那麽假設,開啟1萬個線程,創建1萬個ThreadLocal,也就是每個線程維護1萬個ThreadLocal小內存空間,而且當線程執行結束以後,假設這些ThreadLocal裏的Entry還不會被回收,那麽將很容易導致堆內存溢出。 怎麽辦?難道JVM就沒有提供什麽解決方案嗎? ThreadLocal當然有想到,所以他們把ThreadLocal裏的Entry設置為弱引用,當垃圾回收的時候,回收ThreadLocal。 什麽是弱引用?
  1. Key使用強引用:也就是上述說的情況,引用的ThreadLocal的對象被回收了,ThreadLocal的引用ThreadLocalMap的Key為強引用並沒有被回收,如果不手動回收的話,ThreadLocal將不會回收那麽將導致內存泄漏。
  2. Key使用弱引用:引用的ThreadLocal的對象被回收了,ThreadLocal的引用ThreadLocalMap的Key為弱引用,如果內存回收,那麽將ThreadLocalMap的Key將會被回收,ThreadLocal也將被回收。value在ThreadLocalMap調用get、set、remove的時候就會被清除。
  3. 比較兩種情況,我們可以發現:由於ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動刪除對應key,都會導致內存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除
那按你這麽說,既然JVM有保障了,還有什麽內存泄漏可言? ThreadLocalMap使用ThreadLocal對象作為弱引用,當垃圾回收的時候,ThreadLocalMap中Key將會被回收,也就是將Key設置為null的Entry。如果線程遲遲無法結束,也就是ThreadLocal對象將一直不會回收,回顧到上面存在很多線程+TheradLocal,那麽也將導致內存泄漏。 其實,在ThreadLocal中,當調用remove、get、set方法的時候,會清除為null的弱引用,也就是回收ThreadLocal。 總結:
  1. JVM利用設置ThreadLocalMap的Key為弱引用,來避免內存泄露。
  2. JVM利用調用remove、get、set方法的時候,回收弱引用。
  3. 當ThreadLocal存儲很多Key為null的Entry的時候,而不再去調用remove、get、set方法,那麽將導致內存泄漏。
  4. 當使用static ThreadLocal的時候,延長ThreadLocal的生命周期,那也可能導致內存泄漏。因為,static變量在類未加載的時候,它就已經加載,當線程結束的時候,static變量不一定會回收。那麽,比起普通成員變量使用的時候才加載,static的生命周期加長將更容易導致內存泄漏危機。http://www.importnew.com/22039.html

、版權聲明

  作者:邱勇Aaron

  出處:http://www.cnblogs.com/qiuyong/

  您的支持是對博主深入思考總結的最大鼓勵。

  本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,尊重作者的勞動成果。

  參考:馬士兵並發編程、並發編程實踐

     ThreadLocal源碼分析:http://www.cnblogs.com/digdeep/p/4510875.html

     ThradLocal內存分析實例:http://blog.xiaohansong.com/2016/08/09/ThreadLocal-leak-analyze/      ThreadLoal導致內存泄漏:http://www.importnew.com/22039.html

並發編程(四):ThreadLocal從源碼分析總結到內存泄漏