<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      淺談ThreadLocal----每個線程一個小書包

      ThreadLocal是什么?
      thread是線程,local是本地的意思
      字面意思是線程本地。
      其實更通俗的理解是給每個線程設置一個緩存。這個緩存用來存儲當前線程在未來的業務邏輯中需要執行到的變量。
      我們先來看怎么用:

      首先創建全局變量ThreadLocal,
      各自啟動一個線程任務:
      線程任務將變量設置到緩存中。
      線程任務需要用到緩存中的變量時,直接從緩存中取即可。

       1 import java.util.concurrent.TimeUnit;
       2 
       3 /**
       4  * @discription
       5  */
       6 public class ThreadLocalLearn {
       7     static ThreadLocal<String> threadLocal = new ThreadLocal<>();
       8 
       9     public static void main(String[] args) {
      10         Runnable r = new Runnable() {
      11             @Override
      12             public void run() {
      13                 threadLocal.set(Thread.currentThread().getName());
      14                 sayMyName();
      15                 threadLocal.remove();
      16             }
      17 
      18             public void sayMyName() {
      19                 for (int i = 0; i < 3; i++) {
      20                     String name = threadLocal.get();
      21                     System.out.println(Thread.currentThread().getName() + " say: im a thread, name:" + name);
      22                     try {
      23                         TimeUnit.SECONDS.sleep(3);
      24                     } catch (Exception e) {
      25                         //...
      26                     }
      27                 }
      28             }
      29         };
      30         Thread t1 = new Thread(r);
      31         t1.start();
      32         Thread t2 = new Thread(r);
      33         t2.start();
      34     }
      35 }

      它的使用非常簡單,
      (1)先set()存儲值;
      (2)使用時get()取出值;
      (3)用完了使用remove()清理掉;

      輸出如下:

      Connected to the target VM, address: '127.0.0.1:56863', transport: 'socket'
      Thread-0 say: im a thread, name:Thread-0
      Thread-1 say: im a thread, name:Thread-1
      Thread-0 say: im a thread, name:Thread-0
      Thread-1 say: im a thread, name:Thread-1
      Thread-1 say: im a thread, name:Thread-1
      Thread-0 say: im a thread, name:Thread-0
      Disconnected from the target VM, address: '127.0.0.1:56863', transport: 'socket'

      很多人第一次見到ThreadLocal,第一直覺它的實現是用Map<Thread,Object> 。(防盜連接:本文首發自http://www.rzrgm.cn/jilodream/ )但是深入研究之后,你會發現threadLocal的實現要比這樣一個map 精妙的多,也好用的多。
      我們通過查看java源碼,可以依次探索ThreadLocal是如何實現緩存的:

      類整體的關系大概是這樣的:

      tlleitu

       查看源碼,我們可以發現如下特性:

      1、ThreadLocal本身并不是緩存,它只是起到一個緩存的key 的作用。我們每次創建一個ThreadLocal 并不是真正的創建了一個緩存,其實只是創建了一個緩存的標識。
      源碼如下:this 就是ThreadLocal實例

      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);
      8         }
      9     }

      2、真正的緩存保存在Thread中,緩存被定義為:
      ThreadLocal.ThreadLocalMap threadLocals;
      從名字可以發現,這個緩存的類型是在ThreadLocal 中定義的一個靜態內部類。這個類就是用來真正存放緩存的地方。這就像是thread小書包一樣,每個線程有一個自己的獨立的存儲空間。
      設計疑問:它(ThreadLocalMap)為什么沒有定義在Thread類中,畢竟它是Thread的緩存。

      源碼如下:Thread.java

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

       3、查看ThreadLocalMap的源碼,我們發現它并沒有實現Map接口,就像其他map一樣,ThreadLocalMap實現了常用的Map中的set,get,getEntry,setThreshold,,remove 等方法。

      并且它內部使用了線性探測法來解決哈希沖突。
      設計疑問:它(ThreadLocalMap)為什么沒有實現Map接口?
      源碼如下:ThreadLocal.Java

       1 static class ThreadLocalMap {
       2 
       3         //...
       4 
       5         private static final int INITIAL_CAPACITY = 16;
       6 
       7 
       8         private Entry[] table;
       9 
      10 
      11         private int size = 0;
      12 
      13 
      14         private int threshold; // Default to 0
      15 
      16 
      17         private void setThreshold(int len) {
      18             threshold = len * 2 / 3;
      19         }
      20 
      21 
      22         private Entry getEntry(ThreadLocal<?> key) {
      23                  ...
      24         }
      25 
      26 
      27 
      28         private void set(ThreadLocal<?> key, Object value) {
      29         ...
      30         }
      31 
      32 
      33         private void remove(ThreadLocal<?> key) {
      34         ...
      35         }
      36 
      37 
      38         private void rehash() {
      39             ...
      40         }
      41 
      42         private void resize() {
      43            ...
      44         }
      45       ....
      46     }

      4、繼續看源碼,我們發現ThreadLocalMap類像其他Map實現一樣,在內部定義了Entry。并且這個Entry居然繼承了弱引用,弱引用被定義在Entry的key上,而且key的類型是ThreadLocal。

      至于什么是弱引用,我以前的文章中介紹過,請看(淺談Java中的引用   http://www.rzrgm.cn/jilodream/p/6181762.html),一定要對弱引用了解,否則ThreadLocal的核心實現以及它會存在的問題,就無法更深理解了。

      這里又會有疑問,為什么要使用弱引用,使用強引用不好嗎?弱引用萬一被回收導致空引用等問題怎么辦?

      源碼如下:ThreadLocal.Java

      1         static class Entry extends WeakReference<ThreadLocal<?>> {
      2             /** The value associated with this ThreadLocal. */
      3             Object value;
      4 
      5             Entry(ThreadLocal<?> k, Object v) {
      6                 super(k);
      7                 value = v;
      8             }
      9         }

      我們依次回答這幾個問題:
      (1)設計疑問:它(ThreadLocalMap)為什么沒有定義在Thread類中,畢竟它是Thread的緩存
      這恰恰是Thread符合開閉原則的優秀設計。如果是將ThreadLocalMap添加到Thread中,那么Thread類就太重了,以后只要和線程相關的業務都要將代碼添加到Thread中,那Thread就無限膨脹了,變成超級類了,試想什么業務和線程能脫離關系呢?
      況且他們只是類依賴關系而不是組合關系(對類關系不了解的同學可以看我的這篇文章:統一建模語言UML---類圖  http://www.rzrgm.cn/jilodream/p/16693511.html)。

      Map怎么實現,緩存怎么維護,這些都是Thread不需要考慮的,我們就是需要用到你的特性。

      (2)設計疑問:它(ThreadLocalMap)為什么沒有實現Map接口?
      實現接口是為了統一化提供接口,讓外界可以只依賴接口,而不是接口的實現。但是ThreadLocalMap并不是給外界使用的,并不需要暴露出來。他就是為了給ThreadLocal業務使用的。只要完成最核心的Map能力,用空間換時間,將理論時間復雜度推向O(1)即可。因此完全沒有必要實現Map接口。實現了Map接口反而要將內部方法暴露為public,這也不符合最少知道原則。一句話就是沒必要,還添亂。

      (3)為什么要使用弱引用,使用強引用不好嗎?弱引用萬一被回收導致空引用等問題怎么辦?
      我們需要先了解弱引用的特性:當一個變量只有弱引用關聯時,那么在下次GC回收時,不論我們內存是否足夠,都將回收掉該內存。
      第一眼感覺這很危險,畢竟我們非常擔心就是一個變量用著用著突然不能用了,出現空引用了,漫天的空引用這太不可控了。
      其實這完全多慮了,注意看:我們是如何使用緩存的,是通過threadlocal.get(),也就是說我們想要使用緩存就一定要使用threadlocal的實例,也就是強引用,
      有了強引用,使用時就一定不會被回收。因此完全不用擔心使用緩存中,弱引用key突然變為null的情況了。
      那什么時候弱引用key會被回收呢?
      這就是當外界的強引用被手動設置為null時,(防盜連接:本文首發自http://www.rzrgm.cn/jilodream/ )或者是作為局部變量跳出了方法棧,超出生命周期被回收掉了。
      試想一下,真要是發生這兩種情況,那么其實這個緩存也就根本無法再用到了同時,key被盡快回收,反而對內存更有利。
      那么弱引用這么好用,為什么value不設置為弱引用呢?
      其實細想一下就會發現value一定不能設置為弱引用,為什么呢?
      key設置為弱引用,是因為想要使用這個緩存,key就一定要有強引用關聯。而value則不一定有外界強引用關聯,它在外界的強引用可能早就消失了。比如下面這個例子:

       1 import java.util.concurrent.TimeUnit;
       2 
       3 /**
       4  * @discription
       5  */
       6 public class ThreadLocalLearn {
       7     static ThreadLocal<UserInfo> userContext = new ThreadLocal<>();
       8 
       9     public static void main(String[] args) {
      10         Runnable r = new Runnable() {
      11             @Override
      12             public void run() {
      13                 setUserInfo();
      14                 handle();
      15                 userContext.remove();
      16             }
      17 
      18             public void handle() {
      19                 UserInfo user = userContext.get();
      20                 //注意倘若map中的value被定義為弱引用,則此處的user可能為null
      21                 System.out.println(" i am:" + user.toString());
      22                 //do sth
      23                 try {
      24                     TimeUnit.SECONDS.sleep(3);
      25                 } catch (Exception e) {
      26                     //...
      27                 }
      28             }
      29         };
      30         Thread t1 = new Thread(r);
      31         t1.start();
      32         Thread t2 = new Thread(r);
      33         t2.start();
      34     }
      35 
      36     private static void setUserInfo() {
      37         UserInfo user = new UserInfo();// 假裝是從db中獲取的
      38         userContext.set(user);
      39         //跳出該方法后,userInfo的在外部的直接強引用就被回收了
      40     }
      41 }
      42 
      43 class UserInfo {
      44     private String name;
      45     private int age;
      46     
      47     //....
      48 }

      我們在A方法中設置了緩存 currentUserId,跳出A方法,currentUserId在外界的引用被斷開,倘若此時value也被定義為弱引用,value就隨時可能被回收。而我們又可以通過

      (key)Threadlocal  -->  threadLocals(ThreadLocalMap)  -->  entry  -->  value

      這樣的調用關系來拿到緩存value。這樣緩存的使用就不可控了。
      那么value一定不能設置為弱引用或及時回收么?
      并不是,
      其實我們只要在key回收時,順手對value也做一個回收,但是這是GC完成的,再key消失時,聯動對所有線程中關聯的Map都進行一遍清理。(實現過于復雜)
      亦或者清理key(threadlocal)的強引用時,將value的強引用也一并被清理。
      可行,也是ThreadLocal推薦的方式,需要手動調用ThreadLocal.remove 方法。
      在調用remove方法后,ThreadLocalMap會對所有垃圾數據進行清理,還會壓縮哈希表。
      為了解決ThreadLocalMap的value 延遲清理的情況,ThreadLocalMap在set get remove等方法中,都會對ThreadLocalMap存在的這種<null,Object> 垃圾數據進行一定程度的清理(注意這里要分各種情況,具體只能詳細分析源碼了,一篇博文很難說清)。

      (4)這樣又會有一個新的問題,如果key 被回收了,但是value沒有被回收,因此value就常駐內存了,那么value不就會導致內存泄露嗎?
      很不幸,這樣的確是會導致內存的泄露。(這里簡單提一下,java中的內存泄露是指,可以通過強引用關聯到他,gc無法回收掉它。與此同時,業務按照正常邏輯又無法使用到它。也就是又用不到,又回收不掉,就稱之為內存泄露)
      但是這種內存泄露出現的概率非常低。

      它需要同時滿足以下三個條件才可以:
      1、需要線程的生命周期永遠不會結束。如果線程生命周期結束了,那么ThreadLocalMap就會被回收,里邊出現的無其他關聯的key value 也都會被回收。
      這種一般是守護線程或者線程池(線程復用出現)

      2、ThreadLocal在設置為null時,沒有手動調動remove方法

      3、線程中的ThreadLocalMap在后續使用中,沒有再調用任何get set remove方法,也就是線程沒再使用ThreadLocal

      概率低,是不是代表不太需要關注,當然不是。
      因為內存泄露不僅僅是減少了可用內存,還增加了GC負擔,系統性能就會收到影響,這就說的遠了。

      其實ThreadLocal最大的問題,并不是泄露的問題,而是被濫用的問題,不規范使用的問題。很多人把ThreadLocal當成是線程的私有倉庫,所有變量參數都往里邊塞,
      導致寫代碼和維護時,非常不方便,出現問題也給維護人員造成很大的困擾。

      接下來我們簡單說下ThreadLocal的使用(后邊我會再寫一篇,如何使用ThreadLocal,畢竟我們學習技術目的是能夠駕馭它,而不僅僅是知其所以然):
      我們一般是將上下文信息,或者當前需要頻繁使用的,與實際業務直接關系不大的系統數據方便攜帶。放置到thread的小書包中。
      (1)上下文信息
      如我們在controller層,將用戶的上下文信息傳入,如traceId(方便鏈路追蹤),如用戶token,后續可能調用其他鑒權接口等
      (2)解耦數據庫連接等連接池信息,
      比如Springboot運行事務時,我們每次getconnection(),就只使用ThreadLocal中貯存好的這個連接,整個方法使用的是同一個數據庫連接。
      以上場景不使用ThreadLocal可以嗎?
      也可以,他并不是一定要使用。但是你這樣就要把很多的參數傳來傳去,暴露很多的問題。
      甚至在很多第三方實現的框架中,他不支持你傳這些參數,他就是要用通過ThreadLocal來回傳值。

      (3)為線程安全提供了方案,減少了鎖競爭:
      如果說鎖是從資源競爭的角度,解決了數據安全的問題。
      ThreadLocal則是在每個線程中,只保存(只隔離)出與自己當前業務相關的數據。
      注意他只是保證了數據的獨立性,并不是獨立創建了一份副本,(防盜連接:本文首發自http://www.rzrgm.cn/jilodream/ )所以如果使用全局數據放置到value中時,一樣可能會有數據安全問題。(當然這也是不推薦的用法)
      比如有一份UserCache的全局緩存,多線程使用時,
      我可以在全局中對UserCache進行加鎖處理,也可以每個線程獨立引用自己的UserInfo,線程之間互不干擾。結構就像這個樣子:

      全局加鎖:

      tljingzheng

      線程各自引用:

      tlyinyong

      不知講到這里大家還有沒有最初的直覺了,為啥不設計一個全局的  Map<Thread,Object>。這樣不是更簡單,也更好定位問題:

      細想一下,就會發現這樣并不好:
      方案1,全局只有一個Map,value是當前線程的所有緩存數據。那么Object就是一個非常復雜的數據,每次對Object進行讀取都要解析的特別復雜。
      方案2,全局定義的很多個Map,每個map是一個業務的緩存,比如User,就有userMap,token就有tokenMap。先不論Map本來就會有競爭的問題,對于管理大量的Map就是一件頭痛的事情。

      當然還是要根據具體業務來看,不能一概而論,并不能說任何時候使用ThreadLocal更好,使用全局Map更弱

      posted @ 2025-09-29 16:13  王若伊_恩賜解脫  閱讀(506)  評論(0)    收藏  舉報

      主站蜘蛛池模板: 成人欧美一区在线视频| 极品人妻少妇一区二区三区| 中文字幕亚洲人妻一区| 波多野结衣的av一区二区三区| 欧美人与zoxxxx另类| 丁香婷婷在线观看| 激情六月丁香婷婷四房播| 成人午夜福利视频一区二区| 精品三级在线| 国产精品自拍视频第一页| 国产中文字幕精品喷潮| 日韩大片高清播放器| 久久久无码精品亚洲日韩蜜臀浪潮| 中文字幕成人精品久久不卡| 久久99精品国产99久久6男男| aaa少妇高潮大片免费看| 精品 无码 国产观看| 欧美黑人又粗又大又爽免费 | 国产成人综合久久亚洲av| 亚洲一区二区三区激情视频| 91亚洲国产三上悠亚在线播放| 精品视频不卡免费观看| 中文字幕无码不卡一区二区三区| 久久无码中文字幕免费影院| 久久久久免费看成人影片| 熟女一区二区中文在线| 激情综合网激情国产av| 久久精品国产99国产精品严洲 | 国产精品成人中文字幕| 一区二区在线观看成人午夜| 亚洲国产成人资源在线| 信丰县| 国产成人无码性教育视频| 日日碰狠狠躁久久躁96avv| 精品超清无码视频在线观看| 综合色一色综合久久网| 综合激情网一区二区三区| 色AV专区无码影音先锋| 国产精品亚洲专区无码破解版| 999福利激情视频| 亚洲国产精品无码一区二区三区|