国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

ThreadLocal全攻略:使用實戰,源碼分析,內存泄露分析

存儲 存儲軟件
本篇文章我們從ThreadLocal的使用場景、源碼、類結構、內存結構等進行分析說明,最后分析了其引起內存泄露的根本原因。

[[395368]]

前言

說起ThreadLocal即便你沒有直接用到過,它也間接的出現在你使用過的框架里,比如Spring的事物管理,Hibernate的Session管理、logback(和log4j)中的MDC功能實現等。而在項目開發中,比如用到的一些分頁功能的實現往往也會借助于ThreadLocal。

正是因為ThreadLocal的無處不在,所以在面試的時候也經常會被問到它的實現原理、核心API使用以及內存泄露的問題。

而且基于這些問題還可以拓展到線程安全方面、JVM內存管理與分析、Hash算法等等知識點。可見ThreadLocal對開發人員來說是多么的重要的。如果你還沒有全面的了解,那么這篇文章值得你深入學習一下。

什么是ThreadLocal

ThreadLocal是Therad的局部變量的維護類,在Java中是作為一個特殊的變量存儲在。當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

因為每個Thread內有自己的實例副本,且該副本只能由當前Thread使用,也就不存在多線程間共享的問題。

總的來說,ThreadLocal適用于每個線程需要自己獨立的實例且該實例需要在多個方法中被使用,也即變量在線程間隔離而在方法或類間共享的場景。

比如,有一個變量count,在多線程并發時操作count++會出現線程安全問題。但是通過ThreadLocal就可以為每個線程創建只屬于當前線程的count副本,各自操作各自的副本,不會影響到其他線程。

從另外一個角度來說,ThreadLocal是一個數據結構,有點像HashMap,可以保存"key:value"鍵值對,但是一個ThreadLocal只能保存一個鍵值對,各個線程的數據互不干擾。

  1. @Test 
  2. public void test1(){ 
  3.     ThreadLocal<String> localName = new ThreadLocal<>(); 
  4.     // 只提供了一個set方法; 
  5.     localName.set("程序新視界"); 
  6.     // 同時只提供了一個get方法 
  7.     String name = localName.get(); 
  8.     System.out.println(name); 

上述代碼中線程A初始化了一個ThreadLocal對象,并調用set方法,保持了一個值。而這個值只能線程A調用get方法才能獲取到。如果此時線程B調用get方法是無法獲取到的。至于如何實現這一功能的,我們在后面源代碼分析中進行講解,這里知道其功能即可。

ThreadLocal使用實例

上面介紹了使用場景和基本的實現理論,下面我們就來通過一個簡單的實例看一下如何使用ThreadLocal。

  1. public class ThreadLocalMain { 
  2.  
  3.     /** 
  4.      * ThreadLocal變量,每個線程都有一個副本,互不干擾 
  5.      */ 
  6.     public static final ThreadLocal<String> HOLDER = new ThreadLocal<>(); 
  7.  
  8.     public static void main(String[] args) throws Exception { 
  9.         new ThreadLocalMain().execute(); 
  10.     } 
  11.  
  12.     public void execute() throws Exception { 
  13.         // 主線程設置值 
  14.         HOLDER.set("程序新視界"); 
  15.         System.out.println(Thread.currentThread().getName() + "線程ThreadLocal中的值:" + HOLDER.get()); 
  16.  
  17.         new Thread(() -> { 
  18.             System.out.println(Thread.currentThread().getName() + "線程ThreadLocal中的值:" + HOLDER.get()); 
  19.             // 設置當前線程中的值 
  20.             HOLDER.set("《程序新視界》"); 
  21.             System.out.println("重新設置之后," + Thread.currentThread().getName() + "線程ThreadLocal中的值:" + HOLDER.get()); 
  22.             System.out.println(Thread.currentThread().getName() + "線程執行結束"); 
  23.         }).start(); 
  24.         // 等待所有線程執行結束 
  25.         Thread.sleep(1000L); 
  26.         System.out.println(Thread.currentThread().getName() + "線程ThreadLocal中的值:" + HOLDER.get()); 
  27.     } 
  28.  

示例中定義了一個static final的ThreadLocal變量HOLDER,在main方法中模擬通過兩個線程來操作HOLDER中存儲的值。先對HOLDER設置一個值,然后打印獲取得到的值,然后新起一個線程去修改HOLDER中的值,然后分別在新線程和主線程兩處獲取對應的值。

執行程序,打印結果如下:

  1. main線程ThreadLocal中的值:程序新視界 
  2. Thread-0線程ThreadLocal中的值:null 
  3. 重新設置之后,Thread-0線程ThreadLocal中的值:《程序新視界》 
  4. Thread-0線程執行結束 
  5. main線程ThreadLocal中的值:程序新視界 

對照程序和輸出結果,你會發現,主線程和Thread-0各自獨享自己的變量存儲。主線程并沒有因為Thread-0調用了HOLDER的set方法而被改變。

之所以能達到這個效果,正是因為在ThreadLocal中,每個線程Thread擁有一份自己的副本變量,多個線程互不干擾。那么,你會疑惑,ThreadLocal是如何實現這一功能的呢?

ThreadLocal原理分析

在學習ThreadLocal的原理之前,我們先來看一些相關的理論知識和數據結構。

基本流程與源碼實現

一個線程內可以存多個ThreadLocal對象,存儲的位置位于Thread的ThreadLocal.ThreadLocalMap變量,在Thread中有如下變量:

  1. /* ThreadLocal values pertaining to this thread. This map is maintained 
  2.  * by the ThreadLocal class. */ 
  3. ThreadLocal.ThreadLocalMap threadLocals = null

ThreadLocalMap是由ThreadLocal維護的靜態內部類,正如代碼中注解所說這個變量是由ThreadLocal維護的。

我們在使用ThreadLocal的get()、set()方法時,其實都是調用了ThreadLocalMap類對應的get()、set()方法。

Thread中的這個變量的初始化通常是在首次調用ThreadLocal的get()、set()方法時進行的。

  1. public void set(T value) { 
  2.     Thread t = Thread.currentThread(); 
  3.     ThreadLocalMap map = getMap(t); 
  4.     if (map != null
  5.         map.set(this, value); 
  6.     else 
  7.         createMap(t, value); 

上述set方法中,首先獲取當前線程對象,然后通過getMap方法來獲取當前線程中的threadLocals:

  1. ThreadLocalMap getMap(Thread t) { 
  2.     return t.threadLocals; 

如果Thread中的對應屬性為null,則創建一個ThreadLocalMap并賦值給Thread:

  1. void createMap(Thread t, T firstValue) { 
  2.     t.threadLocals = new ThreadLocalMap(this, firstValue); 

如果已經存在,則通過ThreadLocalMap的set方法設置值,這里我們可以看到set中key為this,也就是當前ThreadLocal對象,而value值則是我們要存的值。

對應的get方法源碼如下:

  1. public T get() { 
  2.     Thread t = Thread.currentThread(); 
  3.     ThreadLocalMap map = getMap(t); 
  4.     if (map != null) { 
  5.         ThreadLocalMap.Entry e = map.getEntry(this); 
  6.         if (e != null) { 
  7.             @SuppressWarnings("unchecked"
  8.             T result = (T)e.value; 
  9.             return result; 
  10.         } 
  11.     } 
  12.     return setInitialValue(); 

可以看到同樣通過當前線程,拿到當前線程的threadLocals屬性,然后從中獲取存儲的值并返回。在get的時候,如果Thread中的threadLocals屬性未進行初始化,則也會間接調用createMap方法進行初始化操作。

下面我們通過一個流程圖來匯總一下上述流程:

上述流程中給Thread的threadLocals屬性初始化的操作,在JDK8和9中通過debug發現,都沒有走createMap方法,暫時還不清楚JVM是如何進行初始化賦值的。而在測試JDK13和JDK14的時候,很明顯走了createMap方法。

ThreadLoalMap的數據結構

ThreadLoalMap是ThreadLocal中的一個靜態內部類,類似HashMap的數據結構,但并沒有實現Map接口。

ThreadLoalMap中初始化了一個大小16的Entry數組,Entry對象用來保存每一個key-value鍵值對。通過上面的set方法,我們已經知道其中的key永遠都是ThreadLocal對象。

看一下相關的源碼:

  1. static class ThreadLocalMap { 
  2.  
  3.     static class Entry extends WeakReference<ThreadLocal<?>> { 
  4.         /** The value associated with this ThreadLocal. */ 
  5.         Object value; 
  6.  
  7.         Entry(ThreadLocal<?> k, Object v) { 
  8.             super(k); 
  9.             value = v; 
  10.         } 
  11.     } 
  12.  
  13.     private static final int INITIAL_CAPACITY = 16; 
  14.  
  15.     // ... 

ThreadLoalMap的類圖結構如下:

這里需要留意的是,ThreadLocalMap類中的Entry對象繼承自WeakReference,也就是說它是弱引用。這里會出現內存泄露的情況,后續會講到。

由于hreadLocalMaps是延遲創建的,因此在構造時至少要創建一個Entry對象。這里可以從構造方法中看到:

  1. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 
  2.     table = new Entry[INITIAL_CAPACITY]; 
  3.     int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 
  4.     table[i] = new Entry(firstKey, firstValue); 
  5.     size = 1; 
  6.     setThreshold(INITIAL_CAPACITY); 

上述構造方法,創建了一個默認長度為16的Entry數組,通過hashCode與length位運算確定索引值i。而上面也提到,每個Thread都有一個ThreadLocalMap類型的變量。

至此,結合Thread,我們可以看到整個數據模型如下:

hash沖突及解決

我們留意到構造方法中Entry在table中存儲位置是通過hashcode算法獲得。每個ThreadLocal對象都有一個hash值threadLocalHashCode,每初始化一個ThreadLocal對象,hash值就增加一個固定的大小0x61c88647。

在向ThreadLocalMap中的Entry數值存儲Entry對象時,會根據ThreadLocal對象的hash值,定位到table中的位置i。這里分三種情況:

  • 如果當前位置為空的,直接將Entry存放在對應位置;
  • 如果位置i已經有值且這個Entry對象的key正好是即將設置的key,那么重新設置Entry中的value;
  • 如果位置i的Entry對象和即將設置的key沒關系,則尋找一個空位置;

計算hash值便會有hash沖突出現,常見的解決方法有:再哈希法、開放地址法、建立公共溢出區、鏈式地址法等。

上面的流程可以看出這里采用的是開放地址方法,如果當前位置有值,就繼續尋找下一個位置,注意table[len-1]的下一個位置是table[0],就像是一個環形數組,所以也叫閉散列法。如果一直都找不到空位置就會出現死循環,發生內存溢出。當然有擴容機制,一般不會找不到空位置的。

ThreadLocal內存泄露

ThreadLocal使用不當可能會出現內存泄露,進而可能導致內存溢出。下面我們就來分析一下內存泄露的原因及相關設計思想。

內存引用鏈路

根據前面對ThreadLocal的分析,得知每個Thread維護一個ThreadLocalMap,它key是ThreadLocal實例本身,value是業務需要存儲的Object。也就是說ThreadLocal本身并不存儲值,它只是作為一個key來讓線程從ThreadLocalMap獲取value。

仔細觀察ThreadLocalMap,這個map是使用ThreadLocal的弱引用作為Key的,弱引用的對象在GC時會被回收。因此使用了ThreadLocal后,引用鏈如圖所示:

其中虛線表示弱引用。下面我們先來了解一下Java中引用的分類。

Java中的引用

Java中通常會存在以下類型的引用:強引用、弱引用、軟引用、虛引用。

  • 強引用:通常new出來的對象就是強引用類型,只要引用存在,垃圾回收器將永遠不會回收被引用的對象,哪怕內存不足的時候;
  • 軟引用:使用SoftReference修飾的對象被稱為軟引用,軟引用指向的對象在內存要溢出的時候被回收。如果回收之后,還沒有足夠的內存,才會拋出內存溢出異常;
  • 弱引用:使用WeakReference修飾的對象被稱為弱引用,只要發生垃圾回收,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象實例。
  • 虛引用:虛引用是最弱的引用,在Java中使用PhantomReference進行定義。虛引用中唯一的作用就是用隊列接收對象即將死亡的通知。

泄露原因分析

正常來說,當Thread執行完會被銷毀,Thread.threadLocals指向的ThreadLocalMap實例也隨之變為垃圾,它里面存放的Entity也會被回收。這種情況是不會發生內存泄漏的。

發生內存泄露的場景一般存在于線程池的情況下。此時,Thread生命周期比較長(存在循環使用),threadLocals引用一直存在,當其存放的ThreadLocal被回收(弱引用生命周期比較短)后,對應的Entity就成了key為null的實例,但value值不會被回收。如果此Entity一直不被get()、set()、remove(),就一直不會被回收,也就發生了內存泄漏。

所以,通常在使用完ThreadLocal后需要調用remove()方法進行內存的清除。

比如在web請求當中,我們可以通過過濾器等進行回收方法的調用:

  1. public void doFilter(ServeletRequest request, ServletResponse){ 
  2.     try{ 
  3.         //設置ThreadLocal變量 
  4.         localName.set("程序新視界"); 
  5.         chain.doFilter(request, response) 
  6.     }finally{ 
  7.         //調用remove方法溢出threadLocal中的變量 
  8.         localName.remove(); 
  9.     } 

為什么使用弱引用而不是強引用?

從表面上看內存泄漏的根源在于使用了弱引用,但為什么JDK采用了弱引用的實現而不是強引用呢?

先來看ThreadLocalMap類上的一段注釋:

  1. To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.  
  2.  
  3. 為了協助處理數據比較大并且生命周期比較長的場景,hash table的條目使用了WeakReference作為key。 

為了協助處理數據比較大并且生命周期比較長的場景,hash table的條目使用了WeakReference作為key。

這跟我們想象的有些不同,弱引用反而是為了解決內存存儲問題而專門使用的。

我們先來假設一下,如果key使用強引用,那么在其他持有ThreadLocal引用的對象都回收了,但ThreadLocalMap依舊持有ThreadLocal的強引用,這就導致ThreadLocal不會被回收,從而導致Entry內存泄露。

對照一下,弱引用的情況。持有ThreadLocal引用的對象都回收了,ThreadLocalMap持有的是ThreadLocal的弱引用,會被自動回收。只不過對應的value值,需要在下次調用set/get/remove方法時會被清除。

綜合對比會發現,采用弱引用反而多了一層保障,ThreadLocal被清理后key為null,對應的value在下一次ThreadLocalMap調用set、get、remove的時候可能會被清除。

所以,內存泄露的根本原因是是否手動清除操作,而不是弱引用。

ThreadLocal應用場景

最后,我們再來回顧一下ThreadLocal的應用場景:

  • 線程間數據隔離,各線程的ThreadLocal互不影響;
  • 方便同一個線程使用某一對象,避免不必要的參數傳遞;
  • 全鏈路追蹤中的traceId或者流程引擎中上下文的傳遞一般采用ThreadLocal;
  • Spring事務管理器采用了ThreadLocal;
  • Spring MVC的RequestContextHolder的實現使用了ThreadLocal;

小結

本篇文章我們從ThreadLocal的使用場景、源碼、類結構、內存結構等進行分析說明,最后分析了其引起內存泄露的根本原因。通過本篇文章的學習,基本上能掌握ThreadLocal百分之百九十的核心知識點。你學到了嗎?

 

責任編輯:武曉燕 來源: 程序新視界
相關推薦

2024-10-31 09:24:42

2022-10-21 11:30:42

用戶生命周期分析

2013-06-08 11:13:00

Android開發XML解析

2009-02-20 11:43:22

UNIXfish全攻略

2025-04-02 09:33:01

2025-06-26 05:00:00

2023-05-29 07:17:48

內存溢出場景

2009-07-17 17:43:49

Jruby開發Web

2010-04-23 14:04:23

Oracle日期操作

2024-05-07 09:01:21

Queue 模塊Python線程安全隊列

2013-04-15 10:48:16

Xcode ARC詳解iOS ARC使用

2023-10-13 19:42:00

2025-05-28 08:45:00

2009-07-04 11:05:48

Linux安全攻略

2014-03-19 17:22:33

2009-10-19 15:20:01

家庭綜合布線

2009-12-14 14:32:38

動態路由配置

2011-07-19 20:36:56

2021-01-29 17:40:00

Flyme安卓手機安全

2018-10-25 15:24:10

ThreadLocal內存泄漏Java
點贊
收藏

51CTO技術棧公眾號

日韩视频久久| 一级成人国产| 欧美日韩日本国产| 国产日韩精品久久| 91超碰国产在线| 国产精品美女久久久久av超清| 最近中文字幕mv免费高清在线| 午夜激情久久| 久久久久久夜| 亚洲欧美日韩在线高清直播| 一本色道久久99精品综合| 日本蜜桃在线观看视频| 亚洲制服av| 正在播放欧美视频| 亚洲综合在线网站| 婷婷成人影院| 91精品国产品国语在线不卡| 亚洲国产精品综合| 风间由美一区二区av101| 欧美性猛交xxxx乱大交3| 黄色a级片免费看| 在线看成人短视频| 欧美日韩国产a| h视频在线观看免费| 久色成人在线| 最近2019免费中文字幕视频三| 国产专区视频| 一道在线中文一区二区三区| 啄木系列成人av电影| 亚洲在线网站| 亚洲成a人v欧美综合天堂| 久久精品aaaaaa毛片| 这里视频有精品| 精品国内片67194| 蜜桃传媒九九九| 爽好多水快深点欧美视频| 欧美精品一区视频| 日韩你懂的在线观看| 日韩精品影音先锋| 一区二区电影免费观看| 欧美男女性生活在线直播观看| www.色就是色| 久久久777| 狠狠色狠狠色综合人人| 一区二区三区高清在线观看| 在线日韩第一页| 日韩一二三区在线观看| 蜜桃视频在线免费| 伊人久久成人| 国产精品theporn88| 婷婷伊人综合| 99久久精品免费看国产一区二区三区 | 免费成人在线网站| 欧美久久在线| 青娱乐精品视频| 亚洲免费av网| 国产成人免费在线观看不卡| 中日韩在线视频| 国产成人免费视频| 日本一区二区三区四区五区六区| 午夜综合激情| 欧美综合77777色婷婷| 日本最新不卡在线| 免费观看中文字幕| 国产麻豆成人精品| 丁香花在线影院观看在线播放| 国产成人啪午夜精品网站男同| 黄色三级中文字幕| 国产一区二区免费看| r级无码视频在线观看| 99精品视频免费在线观看| 成人性生生活性生交12| 中文字幕在线观看一区| 男人艹女人在线观看| 亚洲综合在线视频| 一区二区三区入口| 亚洲黄色片网站| 国产精品xvideos88| 一区二区三区四区免费视频| 亚洲久色影视| 亚洲一区二区在线免费观看| 成人免费毛片片v| 国产羞羞视频在线观看| 日韩一区二区不卡| 国产精品久久久爽爽爽麻豆色哟哟 | 91精品91久久久久久| 亚洲日本视频在线| 国产精品欧美激情| 黄色av成人| 欧美 日韩 亚洲 一区| 久久九九久久九九| 欧美69xxxxx| 日韩精品自拍偷拍| av男人一区| 91免费高清视频| 国产欧美另类| 国产三级三级三级看三级| 夜夜精品浪潮av一区二区三区| www.欧美日本韩国| 在线视频一区二区| 综合在线视频| 亚洲国产一区二区在线| 一区二区视频在线| 97超碰在线公开在线看免费| 国内精品久久久久久影视8| 中文字幕永久在线不卡| 四虎国产精品成人免费4hu| 久久久99久久| 美女喷白浆视频| 日本一区二区高清| 岛国在线视频免费看| 欧美性大战久久久久久久蜜臀| 国产精品xxxav免费视频| 成人美女视频在线看| 丝袜美腿一区| 午夜大尺度福利视频| 国产丝袜一区视频在线观看| 17videosex性欧美| 亚洲精品第一国产综合野| 日本中文字幕在线看| 国产精品自在线| 成人免费毛片片v| 免费a级毛片在线播放| 国产精品美女免费看| 激情av综合网| 国产网红女主播精品视频| 成人免费在线视频网址| 亚洲国产综合人成综合网站| 欧美亚洲精品在线| 上原亚衣加勒比在线播放| 欧美国产日韩一区| 99久久婷婷国产综合精品电影 | 可以在线观看的av网站| 尤物yw午夜国产精品视频明星| 噜噜爱69成人精品| av网站观看| 亚洲精品av在线| 美女福利一区| 午夜小视频在线| 免费一级在线观看播放网址| 日韩在线资源| 在线观看欧美日韩| 视频一区二区三区入口| 美女av在线免费观看| 一区二区三区视频免费| 亚洲成人资源| 日韩av视屏| 欧美欧美欧美欧美| 成人av电影在线网| 日本久久黄色| 最新国产在线| 95av在线视频| 欧美午夜www高清视频| 全球中文成人在线| 91在线中文字幕| 亚洲激情自拍视频| 日日夜夜一区| 成人免费观看www在线| 日韩免费在线播放| 国产成人丝袜美腿| 黄色高清在线观看| 国产欧亚日韩视频| 一区二区成人在线视频 | 久久久久国产精品人| 中文视频在线| 精品国产精品一区二区夜夜嗨| 色婷婷av一区| 成人一区二区在线观看| 国产日产精品一区二区三区四区的观看方式 | 日本人妖一区二区| 国产精品一区免费在线| 伊人222成人综合网| 欧美成人高潮一二区在线看| 成人激情黄色网| 久久天天躁夜夜躁狠狠躁2022| 一个色综合网站| 成人av手机在线观看| 在线观看视频免费一区二区三区| 老司机午夜在线| 嫩草视频在线观看| 天天干天天操天天做| 99在线免费视频观看| 国产一区自拍视频| 久久99国产综合精品女同| 欧美日韩国产三级| 中文字幕一区二区三区av| 欧美日韩国产色综合一二三四| 成人三级黄色免费网站| 国产极品尤物在线| 无码精品a∨在线观看中文| 91亚洲va在线va天堂va国| 欧美精品一区二区不卡| 久久久欧美精品sm网站| 成人高清av| 欧洲视频一区二区| 国产精品88av| 亚洲欧美网站在线观看| 欧美人与牲禽动交com| 日韩福利视频在线| 青青a在线精品免费观看|