自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

哥,我還是不懂 ThreadLocal

開(kāi)發(fā) 前端
實(shí)際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點(diǎn)是,如果這個(gè)對(duì)象只存在弱引用,那么在下一次垃圾回收的時(shí)候必然會(huì)被清理掉。

大家好,我是風(fēng)箏

前幾天群里有個(gè)弟弟說(shuō)看 TheadLocal 有點(diǎn)懵,我就把之前寫(xiě)的那篇給他扔過(guò)去了,結(jié)果他看完了跟我說(shuō):哥,我還是沒(méi)看懂?。?/p>

什么,這意思就是我寫(xiě)的那篇文章不行啊,看完了也看不懂,這怎么能行。于是我問(wèn)他現(xiàn)在糾結(jié)在哪里了,啥地方不懂。經(jīng)過(guò)一番溝通,我發(fā)現(xiàn)那篇文章確實(shí)寫(xiě)得不太行,好多新手不理解的點(diǎn)都沒(méi)有點(diǎn)出來(lái)。

具體的一些容易讓人迷糊的點(diǎn)有以下幾個(gè),雖然有一些問(wèn)題看起來(lái)很傻,但是它們確實(shí)存在。

  1. ThreadLocal 存的值在不同線程間怎么傳遞?
  2. ThreadLocal 以什么形式存儲(chǔ)?
  3. ThreadLocal 可不可以放多個(gè)值?
  4. ThreadLocal 到底是存在哪?跟線程有什么關(guān)系?

咱們上來(lái)先看一段代碼精神精神,接下來(lái)再一一解釋上面的問(wèn)題。這段代碼中聲明了兩個(gè) ThreadLocal ,然后在線程0和線程1中分別賦值這兩個(gè) ThreadLocal,第三個(gè)線程不賦值,在每個(gè)線程中打印這兩個(gè) ThreadLocal 的值。

看一下應(yīng)該輸出的值是多少。

public static void main(String[] args) throws InterruptedException {  
    ThreadLocal<String> threadLocal1 = ThreadLocal.withInitial(() -> "啥都沒(méi)干,初始值");  
    ThreadLocal<String> threadLocal2 = new ThreadLocal<>();  
    Thread thread0 = new Thread() {  
        @Override  
        public void run() {  
            threadLocal1.set("我是threadLocal1 「Thread0」給我賦的值");  
            threadLocal2.set("我是threadLocal2 「Thread0」給我賦的值");  
            String name = "Thread0-";  
            System.out.println(name + "threadLocal1 = " + threadLocal1.get());  
            System.out.println(name + "threadLocal2 = " + threadLocal2.get());  
        }  
    };  
    thread0.start();  
    thread0.join();  
    System.out.println();  
  
    Thread thread1 = new Thread() {  
        @Override  
        public void run() {  
            threadLocal1.set("我是threadLocal1 「Thread1」給我賦的值");  
            threadLocal2.set("我是threadLocal2 「Thread1」給我賦的值");  
            String name = "Thread1-";  
            System.out.println(name + "threadLocal1 = " + threadLocal1.get());  
            System.out.println(name + "threadLocal = " + threadLocal2.get());  
        }  
    };  
    thread1.start();  
    thread1.join();  
    System.out.println();  
  
    Thread thread2 = new Thread() {  
        @Override  
        public void run() {  
            String name = "Thread2-";  
            System.out.println(name + "threadLocal1 = " + threadLocal1.get());  
        }  
    };  
    thread2.start();  
}

下面是輸出的值,看看是不是和你理解的一致。

Thread0-threadLocal1 = 我是threadLocal1 「Thread0」給我賦的值 Thread0-threadLocal2 = 我是threadLocal2 「Thread0」給我賦的值

Thread1-threadLocal1 = 我是threadLocal1 「Thread1」給我賦的值 Thread1-threadLocal = 我是threadLocal2 「Thread1」給我賦的值

Thread2-threadLocal1 = 啥都沒(méi)干,初始值

如果和你想的輸出是一樣的,那你可能已經(jīng)理解了 TheadLocal 了,如果不一致的話,那說(shuō)明你還沒(méi)有掌握它。

問(wèn)題1:ThreadLocal 存的值在不同線程間怎么傳遞?

我聽(tīng)到這個(gè)問(wèn)題有些詫異了,你真是一點(diǎn)兒都沒(méi)懂啊。ThreadLocal 當(dāng)然不需要在進(jìn)程間傳遞了,ThreadLocal 的初衷就是為了不在進(jìn)程間傳遞值,而只是在當(dāng)前線程的各個(gè)地方都能獲取到。

這就要說(shuō)到 ThreadLocal 的定義和應(yīng)用場(chǎng)景了。

ThreadLocal 定義以及使用場(chǎng)景

ThreadLocal允許每個(gè)線程獨(dú)立存儲(chǔ)和訪問(wèn)線程本地變量。線程本地變量是指每個(gè)線程都有自己獨(dú)立的變量副本,互不干擾。這對(duì)于多線程編程來(lái)說(shuō)非常有用,因?yàn)樗试S在每個(gè)線程中存儲(chǔ)狀態(tài)或數(shù)據(jù),而不需要擔(dān)心線程間的競(jìng)爭(zhēng)條件。

我們進(jìn)到 ThreadLocal 的源碼中,通過(guò)源碼注釋就可以看到很清晰的解釋:它是線程的局部變量,這些變量只能在這個(gè)線程內(nèi)被讀寫(xiě),在其他線程內(nèi)是無(wú)法訪問(wèn)的。ThreadLocal 定義的通常是與線程關(guān)聯(lián)的私有靜態(tài)字段(例如,用戶ID或事務(wù)ID)。

變量有局部的還有全局的,局部變量沒(méi)什么好說(shuō)的,一涉及到全局,那自然就會(huì)出現(xiàn)多線程的安全問(wèn)題,要保證多線程安全訪問(wèn),不出現(xiàn)臟讀臟寫(xiě),那就要涉及到線程同步了。而 ThreadLocal 相當(dāng)于提供了介于局部變量與全局變量中間的這樣一種線程內(nèi)部的全局變量。

根據(jù) ThreadLocal 的定義,我們就可以知道它的使用場(chǎng)景了。就是當(dāng)我們只想在本身的線程內(nèi)使用的變量,比如這個(gè)線程要存活一段時(shí)間,可以用 ThreadLocal 來(lái)實(shí)現(xiàn),并且這些變量是和線程的生命周期密切相關(guān)的,線程結(jié)束,變量也就銷毀了。

舉幾個(gè)例子說(shuō)明一下:

1、比如線程中處理一個(gè)非常復(fù)雜的業(yè)務(wù),可能方法有很多,那么,使用 ThreadLocal 可以代替一些參數(shù)的顯式傳遞;

2、比如用來(lái)存儲(chǔ)用戶 Session。Session 的特性很適合 ThreadLocal ,因?yàn)?Session 之前當(dāng)前會(huì)話周期內(nèi)有效,會(huì)話結(jié)束便銷毀。我們先籠統(tǒng)的分析一次 web 請(qǐng)求的過(guò)程:

  • 用戶在瀏覽器中訪問(wèn) web 頁(yè)面;
  • 瀏覽器向服務(wù)器發(fā)起請(qǐng)求;
  • 服務(wù)器上的服務(wù)處理程序(例如tomcat)接收請(qǐng)求,并開(kāi)啟一個(gè)線程處理請(qǐng)求,期間會(huì)使用到 Session ;
  • 最后服務(wù)器將請(qǐng)求結(jié)果返回給客戶端瀏覽器。

從這個(gè)簡(jiǎn)單的訪問(wèn)過(guò)程我們看到正好這個(gè) Session 是在處理一個(gè)用戶會(huì)話過(guò)程中產(chǎn)生并使用的,如果單純的理解一個(gè)用戶的一次會(huì)話對(duì)應(yīng)服務(wù)端一個(gè)獨(dú)立的處理線程,那用 ThreadLocal 在存儲(chǔ) Session ,簡(jiǎn)直是再合適不過(guò)了。但是例如 tomcat 這類的服務(wù)器軟件都是采用了線程池技術(shù)的,并不是嚴(yán)格意義上的一個(gè)會(huì)話對(duì)應(yīng)一個(gè)線程。并不是說(shuō)這種情況就不適合 ThreadLocal 了,而是要在每次請(qǐng)求進(jìn)來(lái)時(shí)先清理掉之前的 Session ,一般可以用攔截器、過(guò)濾器來(lái)實(shí)現(xiàn)。

3、在一些多線程的情況下,如果用線程同步的方式,當(dāng)并發(fā)比較高的時(shí)候會(huì)影響性能,可以改為 ThreadLocal 的方式,例如高性能序列化框架 Kyro 就要用 ThreadLocal 來(lái)保證高性能和線程安全;

4、還有像線程內(nèi)上線文管理器、數(shù)據(jù)庫(kù)連接等可以用到 ThreadLocal;

使用方式

ThreadLocal 的使用非常簡(jiǎn)單,最核心的操作就是四個(gè):創(chuàng)建、創(chuàng)建并賦初始值、賦值、取值。

1、創(chuàng)建

ThreadLocal<String> mLocal = new ThreadLocal<>();

2、創(chuàng)建并賦初值。下面代碼表示創(chuàng)建了一個(gè) String 類型的 ThreadLocal 并且重寫(xiě)了 initialValue 方法,并返回初始字符串,之后調(diào)用 get() 方法獲取的值便是 initialValue 方法返回的值。

ThreadLocal<String> mLocal = new ThreadLocal<String>(){
            @Override
            protected String initialValue(){
                return "init value";
            }
        };
System.out.println(mLocal.get());

3、設(shè)置值

mLocal.set("hello");

4、取值

mLocal.get()

實(shí)現(xiàn)原理

前面回答了第一個(gè)問(wèn)題,后面的三個(gè)問(wèn)題就涉及到 ThreadLocal 的原理了。

首先 ThreadLocal 是一個(gè)泛型類,保證可以接受任何類型的對(duì)象,所以你可以在 ThreadLocal 中放基本類型,比如字符串、整型等,也可以放自定義的實(shí)體對(duì)象,還可以放 List、Set、Map 等都沒(méi)有問(wèn)題。

圖片圖片

先來(lái)理清楚 ThreadLocal 對(duì)象的結(jié)構(gòu)與線程的關(guān)系,我解釋一下上圖的意思。

  1. 在 Thread 類中有一個(gè)屬性叫做 threadLocals,這個(gè)屬性的類型是 ThreadLocal.ThreadLocalMap 類型;
  2. ThreadLocal 就是我們會(huì)直接用到的 ThreadLocal 對(duì)象;
  3. ThreadLocal 有個(gè)內(nèi)部類 是 ThreadLocalMap,就是 Thread 類中的的 threadLocals 對(duì)象的類型;
  4. ThreadLocalMap 通過(guò)名稱可以看出這是一個(gè) Map 結(jié)構(gòu),如果你看過(guò) HashMap 的實(shí)現(xiàn),就會(huì)發(fā)現(xiàn)它是個(gè)簡(jiǎn)易版的 HashMap;
  5. ThreadLocalMap 中真正存儲(chǔ)數(shù)據(jù)的是一個(gè) Entry 數(shù)組;
  6. Entry 又是ThreadLocalMap的一個(gè)靜態(tài)內(nèi)部類, 它繼承 WeakReference 弱引用,暫且理解為是一個(gè) key-value 鍵值對(duì);其中涉及的重要對(duì)象大概就是上面這些,了解這些基礎(chǔ)后,能幫我們更清楚的理解原理。

看上去可能有點(diǎn)亂,最簡(jiǎn)單的就是從 set 方法入手看一看。下面是 set 方法代碼

public void set(T value) {
        Thread t = Thread.currentThread(); // 獲取當(dāng)前線程
        ThreadLocalMap map = getMap(t); // 獲取當(dāng)前線程維護(hù)的 threadLocals
        if (map != null)
            map.set(this, value); // 如果 map 不為空,直接添加
        else
            createMap(t, value); //如果 map 為空,先初始化,再添加
    }

調(diào)用 set 方法

ThreadLocal<String> mLocal = new ThreadLocal<>();
mLocal.set("hello");

調(diào)用 ThreadLocal 的 set 方法時(shí),首先獲取到了當(dāng)前線程。

Thread t = Thread.currentThread();

然后獲取當(dāng)前線程維護(hù)的 ThreadLocalMap 對(duì)象。通過(guò) getMap() 方法,t 就是當(dāng)前線程,直接返回當(dāng)前線程中的 threadLocals 屬性。

ThreadLocalMap map = getMap(t); //獲取 ThreadLocalMap

ThreadLocalMap getMap(Thread t) {  
    return t.threadLocals;  
}

如果 map 不為null,說(shuō)明之前設(shè)置過(guò) ThreadLocal 了,那就調(diào)用ThreadLocalMap 的set 方法。

private void set(ThreadLocal<?> key, Object value) {   
    Entry[] tab = table;  
    int len = tab.length;  
    int i = key.threadLocalHashCode & (len-1);  
  
    for (Entry e = tab[i];  
         e != null;  
         e = tab[i = nextIndex(i, len)]) {  
        ThreadLocal<?> k = e.get();  
  
        if (k == key) {  
            e.value = value;  
            return;  
        }  
  
        if (k == null) {  
            replaceStaleEntry(key, value, i);  
            return;  
        }  
    }  
    tab[i] = new Entry(key, value);  
    int sz = ++size;  
    if (!cleanSomeSlots(i, sz) && sz >= threshold)  
        rehash();  
}
int i = key.threadLocalHashCode & (len-1);

計(jì)算索引,tab[i]就是要存儲(chǔ)的位置,后面 for 中的部分就是處理哈希沖突和更新已有值,先不用管這些細(xì)節(jié),之后將 new Entry(key, value)放到 tab[i]的位置,也就是放到 Entry 數(shù)組中了。

這里面 new Entry中的參數(shù) key 和 value 很關(guān)鍵。返回去看 ThreadLocalMap.set 方法調(diào)用時(shí)候的傳參。

map.set(this, value);

key 是什么呢?key 這里傳的是 this,this 是誰(shuí)呢,就是 ThreadLocal 本身,它本身被當(dāng)做 key 了。value 是什么呢?value 就是調(diào)用 ThreadLocal.set(value)時(shí)傳過(guò)來(lái)的泛型的值,是我們調(diào)用方自己設(shè)置的。

后面還有如果 ThreadLocalMap 實(shí)例不存在的話,則要初始化并賦初值的過(guò)程,這部分也不是理解 ThreadLocal 的重點(diǎn),就不具體講了,看代碼都能理解。

所以后面那三個(gè)問(wèn)題也就解決了。

現(xiàn)在再回過(guò)頭去看最開(kāi)始給的那段代碼。

threadLocal1 和 threadLocal2 的聲明是在 main 方法中的,也就是在主線中聲明的,三個(gè)子線程都可以看到的。

而且線程0和線程1都用了兩個(gè) ThreadLocal,所以說(shuō),一個(gè)線程可以用多個(gè) ThreadLocal,因?yàn)樽罱K存儲(chǔ)實(shí)際上是個(gè) Map,多少個(gè)都沒(méi)關(guān)系。

線程 0 和線程1都對(duì)threadLocal1 和 threadLocal2重新設(shè)置值了,然后通過(guò)get方法得到的也是本線程設(shè)置的值。線程2沒(méi)有對(duì) threadLocal1 賦值,所以在調(diào)用get方法后,得到的是threadLocal1最開(kāi)始設(shè)置的初始值,并不是線程0或線程2設(shè)置的值。也印證了線程之間是不會(huì)互相影響的(當(dāng)然,我們通過(guò)上面的分析已經(jīng)了解這個(gè)原理了)。

內(nèi)存泄漏問(wèn)題

實(shí)際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點(diǎn)是,如果這個(gè)對(duì)象只存在弱引用,那么在下一次垃圾回收的時(shí)候必然會(huì)被清理掉。

所以如果 ThreadLocal 沒(méi)有被外部強(qiáng)引用的情況下,在垃圾回收的時(shí)候會(huì)被清理掉的,這樣一來(lái) ThreadLocalMap 中使用這個(gè) ThreadLocal 的 key 也會(huì)被清理掉。但是,value 是強(qiáng)引用,不會(huì)被清理,這樣一來(lái)就會(huì)出現(xiàn) key 為 null 的 value。

ThreadLocalMap 實(shí)現(xiàn)中已經(jīng)考慮了這種情況,在調(diào)用 set()、get()、remove() 方法的時(shí)候,會(huì)清理掉 key 為 null 的記錄。如果說(shuō)會(huì)出現(xiàn)內(nèi)存泄漏,那只有在出現(xiàn)了 key 為 null 的記錄后,沒(méi)有手動(dòng)調(diào)用 remove() 方法,并且之后也不再調(diào)用 get()、set()、remove() 方法的情況下。

這回,理解了嗎?

責(zé)任編輯:武曉燕 來(lái)源: 古時(shí)的風(fēng)箏
相關(guān)推薦

2021-11-01 07:21:37

Flink大數(shù)據(jù)SQL

2022-12-01 17:17:09

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

2021-08-27 14:14:39

ThreadLocal源碼操作

2024-10-28 08:15:32

2020-04-07 08:00:02

Redis緩存數(shù)據(jù)

2021-01-18 11:27:03

Istio架構(gòu)云環(huán)境

2021-12-31 18:24:45

ThreadLocal數(shù)據(jù)庫(kù)對(duì)象

2020-10-14 10:29:58

人工智能

2011-08-03 08:59:46

JavaScript

2022-11-04 08:47:52

底層算法數(shù)據(jù)

2018-04-09 08:17:36

線程ThreadLocal數(shù)據(jù)

2021-05-21 14:26:18

ObjectMap前端

2024-03-18 00:01:00

按鈕鏈接元素

2018-02-25 22:37:34

2019-05-23 08:55:41

代碼開(kāi)發(fā)工具

2019-05-13 09:01:13

程序員職責(zé)產(chǎn)品經(jīng)理

2009-09-29 17:11:23

Hibernate T

2011-07-14 13:50:09

ThreadLocal

2020-01-13 10:51:45

編程內(nèi)存程序

2020-05-11 15:35:46

ChromeFirefox前端
點(diǎn)贊
收藏

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