成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

精通高并發(fā)與多線(xiàn)程,卻不會(huì)用ThreadLocal?

開(kāi)發(fā) 架構(gòu)
概念ThreadLocal 類(lèi)是用來(lái)提供線(xiàn)程內(nèi)部的局部變量。這種變量在多線(xiàn)程環(huán)境下訪(fǎng)問(wèn)(get 和set 方法訪(fǎng)問(wèn))時(shí)能保證各個(gè)線(xiàn)程的變量相對(duì)獨(dú)立于其他線(xiàn)程內(nèi)的變量。ThreadLocal 實(shí)例通常來(lái)說(shuō)都是 private static 類(lèi)型的,用于關(guān)聯(lián)線(xiàn)程和上下文。

[[351029]]

本文轉(zhuǎn)載自微信公眾號(hào)「小菜良記」,作者小菜良記。轉(zhuǎn)載本文請(qǐng)聯(lián)系小菜良記公眾號(hào)。 

ThreadLocal 簡(jiǎn)介

概念ThreadLocal 類(lèi)是用來(lái)提供線(xiàn)程內(nèi)部的局部變量。這種變量在多線(xiàn)程環(huán)境下訪(fǎng)問(wèn)(get 和set 方法訪(fǎng)問(wèn))時(shí)能保證各個(gè)線(xiàn)程的變量相對(duì)獨(dú)立于其他線(xiàn)程內(nèi)的變量。ThreadLocal 實(shí)例通常來(lái)說(shuō)都是 private static 類(lèi)型的,用于關(guān)聯(lián)線(xiàn)程和上下文。

作用

  • 傳遞數(shù)據(jù)

提供線(xiàn)程內(nèi)部的局部變量。可以通過(guò) ThreadLocal 在同一線(xiàn)程,不同組件中傳遞公共變量。

  • 線(xiàn)程并發(fā)

適用于多線(xiàn)程并發(fā)情況下。

  • 線(xiàn)程隔離

每個(gè)線(xiàn)程的變量都是獨(dú)立的,不會(huì)相互影響。

ThreadLocal 實(shí)戰(zhàn)

1. 常見(jiàn)方法

  • ThreadLocal ()

構(gòu)造方法,創(chuàng)建一個(gè) ThreadLocal 對(duì)象

  • void set (T value)

設(shè)置當(dāng)前線(xiàn)程綁定的局部變量

  • T get ()

獲取當(dāng)前線(xiàn)程綁定的局部變量

  • void remove ()

移除當(dāng)前線(xiàn)程綁定的局部變量

2. 為什么要使用 ThreadLocal

首先我們先看一組并發(fā)條件下的代碼場(chǎng)景:

  1. @Data 
  2. public class ThreadLocalTest { 
  3.     private String name
  4.  
  5.     public static void main(String[] args) { 
  6.         ThreadLocalTest tmp = new ThreadLocalTest(); 
  7.         for (int i = 0; i < 4; i++) { 
  8.             Thread thread = new Thread(() -> { 
  9.                 tmp.setName(Thread.currentThread().getName()); 
  10.                 System.out.println(Thread.currentThread().getName() + 
  11.                                    "\t 拿到數(shù)據(jù):" + tmp.getName()); 
  12.             }); 
  13.             thread.setName("Thread-" + i); 
  14.             thread.start(); 
  15.         } 
  16.     } 

我們理想中的代碼輸出結(jié)果應(yīng)該是這樣的:

  1. /** OUTPUT **/ 
  2. Thread-0  拿到數(shù)據(jù):Thread-0 
  3. Thread-1  拿到數(shù)據(jù):Thread-1 
  4. Thread-2  拿到數(shù)據(jù):Thread-2 
  5. Thread-3  拿到數(shù)據(jù):Thread-3 

但是實(shí)際上輸出的結(jié)果卻是這樣的:

  1. /** OUTPUT **/ 
  2. Thread-0  拿到數(shù)據(jù):Thread-1 
  3. Thread-3  拿到數(shù)據(jù):Thread-3 
  4. Thread-1  拿到數(shù)據(jù):Thread-1 
  5. Thread-2  拿到數(shù)據(jù):Thread-2 

順序亂了沒(méi)有關(guān)系,但是我們可以看到 Thread-0 這個(gè)線(xiàn)程拿到的值卻是 Thread-1

從結(jié)果中我們可以看出多個(gè)線(xiàn)程在訪(fǎng)問(wèn)同一個(gè)變量的時(shí)候會(huì)出現(xiàn)異常,這是因?yàn)榫€(xiàn)程間的數(shù)據(jù)沒(méi)有隔離!

并發(fā)線(xiàn)程出現(xiàn)的問(wèn)題?那加鎖不就完事了!這個(gè)時(shí)候你三下五除二的寫(xiě)下了以下代碼:

  1. @Data 
  2. public class ThreadLocalTest { 
  3.  
  4.     private String name
  5.  
  6.     public static void main(String[] args) { 
  7.         ThreadLocalTest tmp = new ThreadLocalTest(); 
  8.         for (int i = 0; i < 4; i++) { 
  9.             Thread thread = new Thread(() -> { 
  10.                 synchronized (tmp) { 
  11.                     tmp.setName(Thread.currentThread().getName()); 
  12.                     System.out.println(Thread.currentThread().getName()  
  13.                                        + "\t" + tmp.getName()); 
  14.                 } 
  15.             }); 
  16.             thread.setName("Thread-" + i); 
  17.             thread.start(); 
  18.         } 
  19.     } 
  20. /** OUTPUT **/ 
  21. Thread-2 Thread-2 
  22. Thread-3 Thread-3 
  23. Thread-1 Thread-1 
  24. Thread-0 Thread-0 

從結(jié)果上看,加鎖好像是解決了上述問(wèn)題,但是 synchronized 常用于多線(xiàn)程數(shù)據(jù)共享的問(wèn)題,而非多線(xiàn)程數(shù)據(jù)隔離的問(wèn)題。這里使用 synchronized 雖然解決了問(wèn)題,但是多少有些不合適,并且 synchronized 屬于重量級(jí)鎖,為了實(shí)現(xiàn)多線(xiàn)程數(shù)據(jù)隔離貿(mào)然的加上synchronized,也會(huì)影響到性能。

加鎖的方法也被否定了,那么該如何解決?不如用 ThreadLocal 牛刀小試一番:

  1. public class ThreadLocalTest { 
  2.  
  3.     private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); 
  4.  
  5.     public String getName() { 
  6.         return threadLocal.get(); 
  7.     } 
  8.  
  9.     public void setName(String name) { 
  10.         threadLocal.set(name); 
  11.     } 
  12.  
  13.     public static void main(String[] args) { 
  14.         ThreadLocalTest tmp = new ThreadLocalTest(); 
  15.         for (int i = 0; i < 4; i++) { 
  16.             Thread thread = new Thread(() -> { 
  17.                 tmp.setName(Thread.currentThread().getName()); 
  18.                 System.out.println(Thread.currentThread().getName() +  
  19.                                    "\t 拿到數(shù)據(jù):" + tmp.getName()); 
  20.             }); 
  21.             thread.setName("Thread-" + i); 
  22.             thread.start(); 
  23.         } 
  24.     } 

在查看輸出結(jié)果之前,我們先來(lái)看看代碼發(fā)生了那些變化

首先多了一個(gè) private static 修飾的 ThreadLocal ,然后在 setName 的時(shí)候,我們實(shí)際上是往 ThreadLocal 里面存數(shù)據(jù),在 getName 的時(shí)候,我們是在 ThreadLocal 里面取數(shù)據(jù)。感覺(jué)操作上也是挺簡(jiǎn)單的,但是這樣真的能做到線(xiàn)程間的數(shù)據(jù)隔離嗎,我們?cè)賮?lái)看一看結(jié)果:

  1. /** OUTPUT **/ 
  2. Thread-1  拿到數(shù)據(jù):Thread-1 
  3. Thread-2  拿到數(shù)據(jù):Thread-2 
  4. Thread-0  拿到數(shù)據(jù):Thread-0 
  5. Thread-3  拿到數(shù)據(jù):Thread-3 

從結(jié)果上可以看到每個(gè)線(xiàn)程都能取到對(duì)應(yīng)的數(shù)據(jù)。ThreadLocal 也已經(jīng)解決了多線(xiàn)程之間數(shù)據(jù)隔離的問(wèn)題。

那么我們來(lái)小結(jié)一下,為什么需要使用ThreadLocal,與 synchronized 的區(qū)別是什么

  • synchronized

原理: 同步機(jī)制采用 "以時(shí)間換空間" 的方式,只提供了一份變量,讓不同線(xiàn)程排隊(duì)訪(fǎng)問(wèn)

側(cè)重點(diǎn): 多個(gè)線(xiàn)程之間同步訪(fǎng)問(wèn)資源

  • ThreadLocal

原理: ThreadLocal 采用 "以空間換時(shí)間" 的方式,為每個(gè)線(xiàn)程都提供了一份變量的副本,從而實(shí)現(xiàn)同時(shí)訪(fǎng)問(wèn)而互不干擾

側(cè)重點(diǎn): 多線(xiàn)程中讓每個(gè)線(xiàn)程之間的數(shù)據(jù)相互隔離

3. 內(nèi)部結(jié)構(gòu)

從上面的案例中我們可以看到 ThreadLocal 的兩個(gè)主要方法分別是 set() 和 get()

那我們不妨猜想一下,如果讓我們來(lái)設(shè)計(jì) ThreadLocal ,我們?cè)撊绾卧O(shè)計(jì),是否會(huì)有這樣的想法:每個(gè) ThreadLocal 都創(chuàng)建一個(gè) Map,然后用線(xiàn)程作為 Map 的 key,要存儲(chǔ)的局部變量作為 Map 的 value ,這樣就能達(dá)到各個(gè)線(xiàn)程的局部變量隔離的效果。

 

這個(gè)想法也是沒(méi)錯(cuò)的,早期的 ThreadLocal 便是這樣設(shè)計(jì)的,但是在 JDK 8 之后便更改了設(shè)計(jì),如下:

 

設(shè)計(jì)過(guò)程:

  1. 每個(gè) Thread 線(xiàn)程內(nèi)部都有一個(gè) ThreadLocalMap
  2. ThreadLocalMap 中存儲(chǔ)著以 ThreadLocal 對(duì)象為 key ,線(xiàn)程變量為 value
  3. Thread 內(nèi)部的 Map 是由 ThreadLocal 維護(hù)的,由 ThreadLocal 負(fù)責(zé)向 Map 設(shè)置和獲取線(xiàn)程的變量值
  4. 對(duì)于不同的線(xiàn)程,每次獲取副本值時(shí),別的線(xiàn)程并不能獲取到線(xiàn)程的副本值,這樣就會(huì)形成副本的隔離,互不干擾

注: 每個(gè)線(xiàn)程都要有自己的一個(gè) map,但是這個(gè)類(lèi)就是一個(gè)普通的 Java 類(lèi),并沒(méi)有實(shí)現(xiàn)Map 接口,但是具有類(lèi)似 Map 類(lèi)似的功能。

 

通過(guò)這樣實(shí)現(xiàn)看起來(lái)貌似會(huì)比之前我們猜想的更加復(fù)雜,這樣做的好處是什么呢?

  • 每個(gè) Map 存儲(chǔ)的 Entry 數(shù)量就會(huì)變少,因?yàn)橹暗拇鎯?chǔ)數(shù)量由 Thread 的數(shù)量決定,現(xiàn)在是由 ThreadMap 的數(shù)量決定,在實(shí)際開(kāi)發(fā)中,ThreadLocal 的數(shù)量要更少于Thread 的數(shù)量。
  • 當(dāng) Thread 銷(xiāo)毀之后,對(duì)應(yīng)的 ThreadLocalMap 也會(huì)隨之銷(xiāo)毀,能減少內(nèi)存的使用

4. 源碼分析


 

 

首先我們先看 ThreadLocalMap 中有哪些成員:

 

如果你看過(guò) HashMap 的源碼,肯定會(huì)覺(jué)得這幾個(gè)特別熟悉,其中:

  • INITIAL_CAPACITY:初始容量,必須是 2 的整次冪
  • table:存放數(shù)據(jù)的table
  • size:數(shù)組中 entries 的個(gè)數(shù),用于判斷 table 當(dāng)前使用量是否超過(guò)閾值
  • threshold:進(jìn)行擴(kuò)容的閾值,表使用量大于它的時(shí)候會(huì)進(jìn)行擴(kuò)容

ThreadLocals

Thread 類(lèi)中有個(gè)類(lèi)型為 ThreadLocal.ThreadLocalMap 類(lèi)型的變量 ThreadLocals ,這個(gè)就是用來(lái)保存每個(gè)線(xiàn)程的私有數(shù)據(jù)。

 

ThreadLocalMap

ThreadLocalMap是ThreadLocal的內(nèi)部類(lèi),每個(gè)數(shù)據(jù)用Entry保存,其中的Entry用一個(gè)鍵值對(duì)存儲(chǔ),鍵為T(mén)hreadLocal的引用。

 

我們可以看到 Entry 繼承于WeakReference,這是因?yàn)槿绻菑?qiáng)引用,即使把ThreadLocal 設(shè)置為 null,GC 也不會(huì)回收,因?yàn)? ThreadLocalMap 對(duì)它有強(qiáng)引用。

在沒(méi)有手動(dòng)刪除這個(gè)Entry以及CurrentThread依然運(yùn)行的前提下,始終有強(qiáng)引用鏈threadRef -> currentThread -> threadLocalMap -> entry,Entry就不會(huì)被回收(Entry中包括了ThreadLocal實(shí)例和value),導(dǎo)致Entry內(nèi)存泄漏。

 

那是不是就是說(shuō)如果使用了弱引用,就不會(huì)造成內(nèi)存泄露 呢,這也是不正確的。

因?yàn)槿绻覀儧](méi)有手動(dòng)刪除 Entry 的情況下,此時(shí) Entry 中的 key == null,這個(gè)時(shí)候沒(méi)有任何強(qiáng)引用指向 threaLocal 實(shí)例,所以 threadLocal 就可以順利被 gc 回收,但是value 不會(huì)被回收,而這塊的 value 永遠(yuǎn)不會(huì)被訪(fǎng)問(wèn)到,因此會(huì)導(dǎo)致內(nèi)存泄露


 

 

接下來(lái)我們看下 ThreadLocalMap 的幾個(gè)核心方法:

set 方法

首先我們先看下源碼:

  1. public void set(T value) { 
  2.     // 獲取當(dāng)前線(xiàn)程對(duì)象 
  3.     Thread t = Thread.currentThread(); 
  4.     // 獲取此線(xiàn)程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象 
  5.     ThreadLocalMap map = getMap(t); 
  6.     // 判斷map是否存在 
  7.     if (map != null
  8.         // 存在則調(diào)用map.set設(shè)置此實(shí)體entry 
  9.         map.set(this, value); 
  10.     else 
  11.         // 如果當(dāng)前線(xiàn)程不存在ThreadLocalMap對(duì)象則調(diào)用createMap進(jìn)行ThreadLocalMap對(duì)象的初始化 
  12.         // 并將 t(當(dāng)前線(xiàn)程)和value(t對(duì)應(yīng)的值)作為第一個(gè)entry存放至ThreadLocalMap中 
  13.         createMap(t, value); 
  14.  
  15. ThreadLocalMap getMap(Thread t) { 
  16.     return t.threadLocals; 
  17.  
  18. void createMap(Thread t, T firstValue) { 
  19.     //這里的this是調(diào)用此方法的threadLocal 
  20.     t.threadLocals = new ThreadLocalMap(this, firstValue); 

執(zhí)行流程:

  • 首先獲取當(dāng)前線(xiàn)程,并根據(jù)當(dāng)前線(xiàn)程獲取一個(gè) map
  • 如果獲取的 map 不為空,則將參數(shù)設(shè)置到 map 中(當(dāng)前 ThreadLocal 的引用作為key )
  • 如果 Map 為空,則給該線(xiàn)程創(chuàng)建 map ,并設(shè)置初始值

get 方法

源碼如下:

  1. public T get() { 
  2.     // 獲取當(dāng)前線(xiàn)程對(duì)象 
  3.     Thread t = Thread.currentThread(); 
  4.     // 獲取此線(xiàn)程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象 
  5.     ThreadLocalMap map = getMap(t); 
  6.     // 如果此map存在 
  7.     if (map != null) { 
  8.         // 以當(dāng)前的ThreadLocal 為 key,調(diào)用getEntry獲取對(duì)應(yīng)的存儲(chǔ)實(shí)體e 
  9.         ThreadLocalMap.Entry e = map.getEntry(this); 
  10.         // 對(duì)e進(jìn)行判空  
  11.         if (e != null) { 
  12.             @SuppressWarnings("unchecked"
  13.             // 獲取存儲(chǔ)實(shí)體 e 對(duì)應(yīng)的 value值 
  14.             // 即為我們想要的當(dāng)前線(xiàn)程對(duì)應(yīng)此ThreadLocal的值 
  15.             T result = (T)e.value; 
  16.             return result; 
  17.         } 
  18.     } 
  19.     return setInitialValue(); 
  20.  
  21. private T setInitialValue() { 
  22.     // 調(diào)用initialValue獲取初始化的值 
  23.     // 此方法可以被子類(lèi)重寫(xiě), 如果不重寫(xiě)默認(rèn)返回null 
  24.     T value = initialValue(); 
  25.     // 獲取當(dāng)前線(xiàn)程對(duì)象 
  26.     Thread t = Thread.currentThread(); 
  27.     // 獲取此線(xiàn)程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象 
  28.     ThreadLocalMap map = getMap(t); 
  29.     // 判斷map是否存在 
  30.     if (map != null
  31.         // 存在則調(diào)用map.set設(shè)置此實(shí)體entry 
  32.         map.set(this, value); 
  33.     else 
  34.         // 如果當(dāng)前線(xiàn)程不存在ThreadLocalMap對(duì)象則調(diào)用createMap進(jìn)行ThreadLocalMap對(duì)象的初始化 
  35.         // 并將 t(當(dāng)前線(xiàn)程)和value(t對(duì)應(yīng)的值)作為第一個(gè)entry存放至ThreadLocalMap中 
  36.         createMap(t, value); 
  37.     // 返回設(shè)置的值value 
  38.     return value; 

執(zhí)行流程:

  • 首先獲取當(dāng)前線(xiàn)程,根據(jù)當(dāng)前線(xiàn)程獲取一個(gè) map
  • 如果獲取的 map 不為空,則在 map 中以 ThreadLocal 的引用作為 key 來(lái)在 map 中獲取對(duì)應(yīng)的 Entry entry ,否則跳轉(zhuǎn)到第四步
  • 如果 Entry entry 不為空 ,則返回 entry.value ,否則跳轉(zhuǎn)到第四步
  • map 為空或者 entry 為空,則通過(guò) initialValue 函數(shù)獲取初始值 value ,然后用ThreadLocal 的引用和 value 作為 firstKey 和 firstValue 創(chuàng)建一個(gè)新的 map

remove 方法

源碼如下:

  1. public void remove() { 
  2.     // 獲取當(dāng)前線(xiàn)程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象 
  3.     ThreadLocalMap m = getMap(Thread.currentThread()); 
  4.     // 如果此map存在 
  5.     if (m != null
  6.         // 存在則調(diào)用map.remove 
  7.         m.remove(this); 
  8. // 以當(dāng)前ThreadLocal為key刪除對(duì)應(yīng)的實(shí)體entry 
  9. private void remove(ThreadLocal<?> key) { 
  10.     Entry[] tab = table
  11.     int len = tab.length; 
  12.     int i = key.threadLocalHashCode & (len-1); 
  13.     for (Entry e = tab[i]; 
  14.          e != null
  15.          e = tab[i = nextIndex(i, len)]) { 
  16.         if (e.get() == key) { 
  17.             e.clear(); 
  18.             expungeStaleEntry(i); 
  19.             return
  20.         } 
  21.     } 

執(zhí)行流程:

首先獲取當(dāng)前線(xiàn)程,并根據(jù)當(dāng)前線(xiàn)程獲取一個(gè) map

如果獲得的map 不為空,則移除當(dāng)前 ThreadLocal 對(duì)象對(duì)應(yīng)的 entry

initialValue 方法

源碼如下:

  1. protected T initialValue() { 
  2.     return null

在源碼中我們可以看到這個(gè)方法僅僅簡(jiǎn)單的返回了 null ,這個(gè)方法是在線(xiàn)程第一次通過(guò)get () 方法訪(fǎng)問(wèn)該線(xiàn)程的 ThreadLocal 時(shí)調(diào)用的,只有在線(xiàn)程先調(diào)用了 set () 方法才不會(huì)調(diào)用 initialValue () 方法,通常情況下,這個(gè)方法最多被調(diào)用一次。

如果們想要 ThreadLocal 線(xiàn)程局部變量有一個(gè)除 null 以外的初始值,那么就必須通過(guò)子類(lèi)繼承 ThreadLocal 來(lái)重寫(xiě)此方法,可以通過(guò)匿名內(nèi)部類(lèi)實(shí)現(xiàn)。

 

責(zé)任編輯:武曉燕 來(lái)源: 小菜良記
相關(guān)推薦

2025-03-07 00:29:37

2021-03-16 15:12:57

CompletableFuture機(jī)制java

2015-02-12 10:24:50

混合云混合云管理戴爾云

2024-08-12 12:25:25

SpringMVC開(kāi)發(fā)

2020-08-26 14:40:38

explainMySQL數(shù)據(jù)庫(kù)

2024-09-09 08:36:36

Java操作遠(yuǎn)程服務(wù)器

2020-09-01 14:17:03

WindowsDefender微軟

2020-05-14 08:59:28

API網(wǎng)關(guān)性能

2020-12-18 09:45:33

DockerLinux命令

2022-02-22 08:25:51

typeScript泛型概念泛型使用

2020-09-27 06:50:56

Java互聯(lián)網(wǎng)注解

2021-04-28 08:00:16

多線(xiàn)程高并發(fā)操作

2012-05-02 15:38:49

金山快盤(pán)網(wǎng)盤(pán)

2020-10-13 07:44:45

理解分布式

2022-08-12 15:58:34

Docker

2019-11-28 16:48:00

華為Mate X

2018-12-20 09:30:59

分布式高并發(fā)多線(xiàn)程

2020-10-21 10:02:16

架構(gòu)運(yùn)維技術(shù)

2018-09-13 10:40:40

Linux命令find

2020-12-07 09:15:00

JavaScript數(shù)組 reduce
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 欧美精品综合在线 | 操网站 | 日韩视频 中文字幕 | av激情影院| 久久国产精品免费 | 美女视频一区二区三区 | 欧美色影院| 不卡视频一区 | 国产一级片av | 好婷婷网 | 日韩精品激情 | 欧美日韩一区在线观看 | 国产一级视频在线观看 | 性一交一乱一透一a级 | 久久中文网 | av网站观看 | 亚洲综合日韩精品欧美综合区 | www.久久艹| 91在线影院| 精品无码久久久久久久动漫 | av国产精品 | 亚洲图片视频一区 | 国产精品夜间视频香蕉 | 一区在线视频 | 国产高清一区二区三区 | 欧美一级二级在线观看 | 国产欧美一区二区三区久久人妖 | 国产精品久久一区二区三区 | 久久久精品久久久 | 日韩国产欧美一区 | 毛片一级电影 | 成人午夜激情 | 国产午夜精品久久久 | 日本久草视频 | 99热在这里只有精品 | 一区二区三区四区免费观看 | 久久国产亚洲 | 午夜在线免费观看 | 天天综合干 | 久久精品亚洲精品国产欧美 | 天天干天天插 |