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

1、目錄

     一、ThreadLocal是什麼?有什麼用?
     二、ThreadLocal源碼簡要總結?
     三、ThreadLocal爲何會致使內存泄漏?

2、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
 
分析:
一、根據預期結果,那ThreadLocal究竟是什麼?
回顧Java內存模型:
  
      在虛擬機中,堆內存用於存儲共享數據(實例對象),堆內存也就是這裏說的主內存。
     每一個線程將會在堆內存中開闢一塊空間叫作線程的工做內存,附帶一塊緩存區用於存儲共享數據副本。那麼,共享數據在堆內存當中,線程通訊就是經過主內存爲中介,線程在本地內存讀而且操做完共享變量操做完畢之後,把值寫入主內存。
  1. ThreadLocal被稱爲線程局部變量,說白了,他就是線程工做內存的一小塊內存,用於存儲數據。
  2. 那麼,ThreadLocal.set()、ThreadLocal.get()方法,就至關於把數據存儲於線程本地,取也是在本地內存讀取。就不會像synchronized須要頻繁的修改主內存的數據,再把數據複製到工做內存,也大大提升訪問效率。
 
二、ThreadLocal到底有什麼用?
  1. 回到最開始的舉例,也就等價於mabatis、hibernate爲何要使用threadlocal來存儲session?
  2. 做用一:由於線程間的數據交互是經過工做內存與主存的頻繁讀寫完成通訊,然而存儲於線程本地內存,提升訪問效率,避免線程阻塞形成cpu吞吐率降低。
  3. 做用二:在多線程中,每個線程都須要維護session,輕易完成對線程獨享資源的操做。
 
總結:
     Threadlocal是什麼?在堆內存中,每一個線程對應一塊工做內存,threadlocal就是工做內存的一小塊內存。
     Threadlocal有什麼用?threadlocal用於存取線程獨享數據,提升訪問效率。

3、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的東西。
 

4、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

 、版權聲明

  做者:邱勇Aaronhtml

  出處: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
相關文章
相關標籤/搜索