深入剖析ThreadLocal實現原理以及內存泄漏問題


原創:https://blog.csdn.net/LHQJ1992/article/details/52451136?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control

一、概述

在2017京東校園招聘筆試題中遇到了描述ThreadLocal的實現原理和內存泄漏的問題,之前看過ThreadLocal的實現原理,但是網上有很多文章將的很亂,其中有很多文章將ThreadLocal與線程同步機制混為一談,特別注意的是ThreadLocal與線程同步無關,並不是為了解決多線程共享變量問題!
ThreadLocal官網解釋:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy ofthevariable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state witha thread (e.g.,a user ID or Transaction ID)

->翻譯過來的大概意思就是:ThreadLocal類用來提供線程內部的局部變量。這些變量在多線程環境下訪問(通過get或set方法訪問)時能保證各個線程里的變量相對獨立於其他線程內的變量,ThreadLocal實例通常來說都是private static類型。
總結:ThreadLocal不是為了解決多線程訪問共享變量,而是為每個線程創建一個單獨的變量副本,提供了保持對象的方法和避免參數傳遞的復雜性。

ThreadLocal的主要應用場景為按線程多實例(每個線程對應一個實例)的對象的訪問,並且這個對象很多地方都要用到。例如:同一個網站登錄用戶,每個用戶服務器會為其開一個線程,每個線程中創建一個ThreadLocal,里面存用戶基本信息等,在很多頁面跳轉時,會顯示用戶信息或者得到用戶的一些信息等頻繁操作,這樣多線程之間並沒有聯系而且當前線程也可以及時獲取想要的數據。

 

二、實現原理

ThreadLocal可以看做是一個容器,容器里面存放着屬於當前線程的變量。ThreadLocal類提供了四個對外開放的接口方法,這也是用戶操作ThreadLocal類的基本方法:
(1) void set(Object value)設置當前線程的線程局部變量的值。
(2) public Object get()該方法返回當前線程所對應的線程局部變量。
(3) public void remove()將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度。
(4) protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,並且僅執行1次,ThreadLocal中的缺省實現直接返回一個null。

可以通過上述的幾個方法實現ThreadLocal中變量的訪問,數據設置,初始化以及刪除局部變量,那ThreadLocal內部是如何為每一個線程維護變量副本的呢?

其實在ThreadLocal類中有一個靜態內部類ThreadLocalMap(其類似於Map),用鍵值對的形式存儲每一個線程的變量副本,ThreadLocalMap中元素的key為當前ThreadLocal對象,而value對應線程的變量副本,每個線程可能存在多個ThreadLocal。

源代碼:

/** Returns the value in the current thread's copy of this thread-local variable. If the variable has no value for thecurrent 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);//獲取當前線程對應的ThreadLocalMapif (map != null) { ThreadLocalMap.Entry e = map.getEntry(this);//獲取對應ThreadLocal的變量值if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();//若當前線程還未創建ThreadLocalMap,則返回調用此方法並在其中調用createMap方法進行創建並返回初始值。 } //設置變量的值publicvoidset(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } /** 為當前線程創建一個ThreadLocalMap的threadlocals,並將第一個值存入到當前map中 @param t the current thread @param firstValue value for the initial entry of the map */void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } //刪除當前線程中ThreadLocalMap對應的ThreadLocalpublicvoidremove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }

上述是在ThreadLocal類中的幾個主要的方法,他們的核心都是對其內部類ThreadLocalMap進行操作,下面看一下該類的源代碼:

static class ThreadLocalMap {//map中的每個節點Entry,其鍵key是ThreadLocal並且還是弱引用,這也導致了后續會產生內存泄漏問題的原因。 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } /** * 初始化容量為16,以為對其擴充也必須是2的指數 */private static final int INITIAL_CAPACITY = 16; /** * 真正用於存儲線程的每個ThreadLocal的數組,將ThreadLocal和其對應的值包裝為一個Entry。 */private Entry[] table; ///....其他的方法和操作都和map的類似 }

總之,為不同線程創建不同的ThreadLocalMap,用線程本身為區分點,每個線程之間其實沒有任何的聯系,說是說存放了變量的副本,其實可以理解為為每個線程單獨new了一個對象。

三、內存泄漏問題(參考其他博文)

  在上面提到過,每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置為null以后,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強引用. 只有當前thread結束以后, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.
  所以得出一個結論就是只要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設為null和線程結束這段時間不會被回收的,就發生了我們認為的內存泄露。其實這是一個對概念理解的不一致,也沒什么好爭論的。最要命的是線程對象不被回收的情況,這就發生了真正意義上的內存泄露。比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。就可能出現內存泄露。

 

ThreadLocal是一個線程內共享變量工具類。 將線程與該線程存放的對象做一個映射,各個線程之間的變量互不干擾。適用於各個線程依賴不同的變量值完成操作的場景,如:Spring聲明式數據庫事務、shiro的session

ThreadLocal內部結構

 

 

 

在這里插入圖片描述

核心機制:
1.每個線程實例中有個threadlocals屬性,實際上是個map
2.這個Map中存放的是ThreadLocal實例和Threadlocal實例在該線程中共享的值(value)
3.線程中的Map有ThreadLocal實例維護,由ThreadLocal實例向map中設置和獲取值。
3.1 設置值
ThreadLocal實例通過Thread.currentThread獲得當前線程實例,自身為key,待設置的值為value組成Entry,放入當前線程實例的map中。
3.2 獲取值
ThreadLocal實例通過Thread.currentThread獲得當前線程實例,在map中以自身為key,獲得對應的value。

從上面的機制中確保threadlocal設置的值,僅在設置時的線程中共享,其它線程無法訪問到該線程中設置的值。確保了,1、線程間變量隔離,2、線程內能訪問。

內存溢出

當線程實例的生命周期短於ThreadLocal實例的生命周期,threadLocal內存的回收,取決於threadLocal實例的生命周期。而,當線程實例的生命周期長於ThreadLocal實例的生命周期(一般線程池場景)時,thread中ThreadLocalMap的每個Entry的key(對threadLocal的弱引用),在gc時會被回收;然而,Entry中value(強引用)是不會回收。當我們使用ThreadLocal的set方法,要配套進行remove,確保value能及時回收。

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable.  {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID)
  • 1

->翻譯過來的大概意思就是:ThreadLocal類用來提供線程內部的局部變量。這些變量在多線程環境下訪問(通過get或set方法訪問)時能保證各個線程里的變量相對獨立於其他線程內的變量,ThreadLocal實例通常來說都是private static類型。
總結:ThreadLocal不是為了解決多線程訪問共享變量,而是為每個線程創建一個單獨的變量副本,提供了保持對象的方法和避免參數傳遞的復雜性。

ThreadLocal的主要應用場景為按線程多實例(每個線程對應一個實例)的對象的訪問,並且這個對象很多地方都要用到。例如:同一個網站登錄用戶,每個用戶服務器會為其開一個線程,每個線程中創建一個ThreadLocal,里面存用戶基本信息等,在很多頁面跳轉時,會顯示用戶信息或者得到用戶的一些信息等頻繁操作,這樣多線程之間並沒有聯系而且當前線程也可以及時獲取想要的數據。

二、實現原理

ThreadLocal可以看做是一個容器,容器里面存放着屬於當前線程的變量。ThreadLocal類提供了四個對外開放的接口方法,這也是用戶操作ThreadLocal類的基本方法:
(1) void set(Object value)設置當前線程的線程局部變量的值。
(2) public Object get()該方法返回當前線程所對應的線程局部變量。
(3) public void remove()將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度。
(4) protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,並且僅執行1次,ThreadLocal中的缺省實現直接返回一個null。

可以通過上述的幾個方法實現ThreadLocal中變量的訪問,數據設置,初始化以及刪除局部變量,那ThreadLocal內部是如何為每一個線程維護變量副本的呢?

其實在ThreadLocal類中有一個靜態內部類ThreadLocalMap(其類似於Map),用鍵值對的形式存儲每一個線程的變量副本,ThreadLocalMap中元素的key為當前ThreadLocal對象,而value對應線程的變量副本,每個線程可能存在多個ThreadLocal。

源代碼:

/** Returns the value in the current thread's copy of this thread-local variable. If the variable has no value for thecurrent 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);//獲取當前線程對應的ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this);//獲取對應ThreadLocal的變量值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();//若當前線程還未創建ThreadLocalMap,則返回調用此方法並在其中調用createMap方法進行創建並返回初始值。 } //設置變量的值 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } /** 為當前線程創建一個ThreadLocalMap的threadlocals,並將第一個值存入到當前map中 @param t the current thread @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } //刪除當前線程中ThreadLocalMap對應的ThreadLocal public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

上述是在ThreadLocal類中的幾個主要的方法,他們的核心都是對其內部類ThreadLocalMap進行操作,下面看一下該類的源代碼:

static class ThreadLocalMap {
  //map中的每個節點Entry,其鍵key是ThreadLocal並且還是弱引用,這也導致了后續會產生內存泄漏問題的原因。
 static class Entry extends WeakReference<ThreadLocal<?>> {
           Object value;
           Entry(ThreadLocal<?> k, Object v) {
               super(k);
               value = v;
   }
    /** * 初始化容量為16,以為對其擴充也必須是2的指數 */
    private static final int INITIAL_CAPACITY = 16;
    /** * 真正用於存儲線程的每個ThreadLocal的數組,將ThreadLocal和其對應的值包裝為一個Entry。 */
    private Entry[] table;


    ///....其他的方法和操作都和map的類似
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

總之,為不同線程創建不同的ThreadLocalMap,用線程本身為區分點,每個線程之間其實沒有任何的聯系,說是說存放了變量的副本,其實可以理解為為每個線程單獨new了一個對象。

三、內存泄漏問題(參考其他博文)

  在上面提到過,每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置為null以后,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強引用. 只有當前thread結束以后, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.
  所以得出一個結論就是只要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設為null和線程結束這段時間不會被回收的,就發生了我們認為的內存泄露。其實這是一個對概念理解的不一致,也沒什么好爭論的。最要命的是線程對象不被回收的情況,這就發生了真正意義上的內存泄露。比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。就可能出現內存泄露。

 
 
1,spring是一個開源的免費的框架(容器)。 2,spring是一個輕量級的,非入侵式的框架。 ​ 非入侵式:就是項目引入了這個框架之后,(不會改變你代碼原來的任何情況)不會對之前的代碼有什么影響
        想必很多朋友對 ThreadLocal並不陌生,今天我們就來一起探討下 ThreadLocal的使用方法和 實現 原理。首先,本文先談一下對 ThreadLocal的理解,然后根據 ThreadLocal類的源碼分析了其 實現 原理和使用需要注意的地方,最后給出了兩個應用場景。 一.對 ThreadLocal的理解          ThreadLocal,很多地方叫做線程本地變量,也有些地方...

  • Monkey_D_Jie
    Monkey_D_Jie : 謝謝博主的說明。 就是--最后內存泄露部分,講的不是很清楚耶 2年前 回復
    3
    • wekajava
      wekajava 回復 : https://www.jianshu.com/p/b74de925cd7a 這里講了應用場景和內存泄漏原因及預防 4月前 回復
  • z15732621582
    碼哥 趙盡朝 : 當然一般情況吧ThreadLocal聲明成static final,這樣可以保持ThreadLocal強引用。 2年前 回復
ThreadLocal是什么早在JDK 1.2的版本中就提供java.lang. ThreadLocalThreadLocal為解決多線程程序的並發 問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多線程程序。 ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程”。其實, ThreadLocal並不是一個Thread,而是Thread的局部變量,也許把它命名為T
8.2 使用 ThreadLocal不當可能會導致 內存泄露 基礎篇已經講解了 ThreadLocal原理,本節着重來講解下使用 ThreadLocal會導致 內存泄露的原因,並講解使用 ThreadLocal導致 內存泄露的案例。 8.2.1 為何會出現 內存泄露 基礎篇我們講到了 ThreadLocal只是一個工具類,具體存放變量的是在線程的 threadLocals變量里面,threadLo
Java多線程編程-(8)-多圖 深入分析 ThreadLocal 原理_徐劉..._CSDN博客
10-24
這一篇主要學習一下 ThreadLocal原理,在下一篇會 深入理解一下OOM 內存溢出的 原理和最佳實踐。  ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程”。
ThreadLocal底層 原理 實現_奮進的小白粥-CSDN博客
10-2
ThreadLocal是java.lang包中的一個類,用來 實現變量的線程封閉性,即只有當前線程可以操作該變量,通過把一個變量存在當前線程的一個Map容器中來 實現。當然,這樣解釋很...
ThreadLocal原理是操作Thread內部的一個 ThreadLocalMap,這個Map的Entry繼承了WeakReference,設值完成后map中是(WeakReference,value)這樣的數據結構。java中的弱引用在 內存不足的時候會被回收掉,回收之后變成(null,value)的形式,key被收回掉了。 如果線程執行完之后銷毀,value也會被回收,這樣也沒 問題。但如果是...
這里使用的servlet容器是 tomcat如果在web項目中,使用  ThreadLocal 不當,會造成 OutOfMemoryError。說明原因前 1:先講一下  ThreadLocal,Thead, ThreadLocalMap 三者之間的一個關系。(大家可以去看一下 ThreadLocal實現源碼,可而參考我的另一篇文章點擊查看)  ThreadLocalMap 是  ThreadLocal
ThreadLocal 原理(簡單易懂)_學習即修行-CSDN博客
11-1
下面我們從一個例子開始,循着源碼,走入 ThreadLocal原理世界吧!(作者目前使用的JDK版本是1.8.0_144版) 使用示例 publicclass ThreadLocalTest{publicstaticvoidmain(St...
圖解 ThreadLocal 原理_wo11201432的專欄-CSDN博客
10-31
在項目中不少地方會用到 ThreadLocal對象,用來 實現線程之間資源隔離。現在通過圖的方式接解刨其 原理。 publicstaticvoidmain(String[]arg)throwsInterruptedException,IOExc...
前言  ThreadLocal 的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的復雜度。但是如果濫用  ThreadLocal,就可能會導致 內存 泄漏。下面,我們將圍繞三個方面來分析  ThreadLocal  內存 泄漏問題  ThreadLocal  實現 原理  ThreadLocal為什么會 內存 泄漏  ThreadLocal 最佳...
前言  原理 為什么key使用弱引用  內存泄露 線程池 參閱:http://www.importnew.com/22039.html 前言  ThreadLocal提供了線程獨有的局部變量,可以在整個線程存活的過程中隨時取用,極大地方便了一些邏輯的 實現。常見的 ThreadLocal用法有: - 存儲單個線程上下文信息。比如存儲id等; - 使變量線程安全。變量既然成為...
徹底搞懂 ThreadLocal 原理_張振偉的博客-CSDN博客
11-1
下面從以下幾個方面徹底搞懂 ThreadLocal: 應用場景  實現 原理 關於 內存 泄漏 使用場景 維護調用鏈路的requestID 在分布式系統中,一個面向用戶的服務往往由內部系統多次調...
ThreadLocal 原理_dakaniu的博客-CSDN博客_ threadlocal 原理
11-24
ThreadLocal的使用及其 原理  ThreadLocal: ThreadLocal是線程局部變量,所謂的線程局部變量,就是僅僅只能被本線程訪問,不能在線程之間進行共享訪問的變量。  ThreadLocal的...
在這篇文章中,總結了一下面試過程中遇到的關於 ThreadLocal的內容。總體上說,這樣回答,面試算是過得去了。但是,這樣的回答,明顯僅僅是背會了答案,而沒有去研究 ThreadLocal的最根本的 實現 原理。 一共有兩個 問題。 1、每個線程的變量副本是存儲在哪里的? 2、變量副本是怎么從共享的那個變量賦值出來的?源碼中的 threadlocal的初始值是什么時機設置的? ===========
西北工業大學計算機組成 原理實驗課唐都儀器實驗幫助,同實驗指導書。分為運算器,存儲器,控制器,模型計算機,輸入輸出系統5個章節
ThreadLocal 原理詳解_karute的博客-CSDN博客_ threadlocal 原理
11-16
如果在應用上為每個線程分配了同一個對象實例,那 ThreadLocal也無法保證線程安全。 4.3.1  ThreadLocal 實現 原理  ThreadLocal的內部 實現,主要關注的就是set()和put()...
深入理解 ThreadLocal原理內存 泄漏 問題_... 永遠年..._CSDN博客
11-14
ThreadLocal作用和 原理分析:  ThreadLocal主要為變量在每個線程中都創建了一個副本,那么每個線程可以訪問自己內部的副本變量。要理解 ThreadLocal需要理解下面三個 問題: ...
©️2020 CSDN  皮膚主題: 技術黑板  設計師:CSDN官方博客  返回首頁
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM