欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程之ThreadLocal原理總結(jié)

 更新時(shí)間:2023年04月21日 11:10:45   作者:奔赴在自己的熱愛中  
這篇文章主要介紹了Java多線程ThreadLocal原理,同一個(gè)ThreadLocal所包含的對(duì)象,在不同的Thread中有不同的副本,文章中有詳細(xì)的代碼示例,需要的朋友參考一下

1、什么是 ThreadLocal:

ThreadLocal,即線程本地變量,如果你創(chuàng)建了一個(gè)變量,那么訪問這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的本地拷貝,多個(gè)線程操作這個(gè)變量的時(shí)候,實(shí)際操作自己本地內(nèi)存里面的變量,從而起到線程隔離的作用,避免了線程安全問題

ThreadLocal 適用于無狀態(tài),副本變量獨(dú)立后不影響業(yè)務(wù)邏輯的高并發(fā)場(chǎng)景,如果業(yè)務(wù)邏輯強(qiáng)依賴于變量副本,則不適合用 ThreadLocal 解決,需要另尋解決方案

應(yīng)用場(chǎng)景:

數(shù)據(jù)庫連接池會(huì)話管理中使用

2、ThreadLocal 的數(shù)據(jù)結(jié)構(gòu):

在 JDK8 中,每個(gè)線程 Thread 內(nèi)部都維護(hù)了一個(gè) ThreadLocalMap 的數(shù)據(jù)結(jié)構(gòu),ThreadLocalMap 中有一個(gè)由內(nèi)部類 Entry 組成的 table 數(shù)組,Entry 的 key 就是線程的本地化對(duì)象 ThreadLocal,而 value 則存放了當(dāng)前線程所操作的變量副本。每個(gè) ThreadLocal 只能保存一個(gè)副本 value,并且各個(gè)線程的數(shù)據(jù)互不干擾,如果想要一個(gè)線程保存多個(gè)副本變量,就需要?jiǎng)?chuàng)建多個(gè)ThreadLocal。

一個(gè) ThreadLocal 的值,會(huì)根據(jù)線程的不同,分散在 N 個(gè)線程中,所以獲取 ThreadLocal 的 value,有兩個(gè)步驟:

第一步,根據(jù)線程獲取 ThreadLocalMap

第二步,根據(jù)自身從 ThreadLocalMap 中獲取值,所以它的 this 就是 Map 的 Key

當(dāng)執(zhí)行 set() 方法時(shí),其值是保存在當(dāng)前線程的 ThreadLocal 變量副本中
當(dāng)執(zhí)行g(shù)et() 方法中,是從當(dāng)前線程的 ThreadLocal 的變量副本獲取。

所以對(duì)于不同的線程,每次獲取副本值時(shí),別的線程并不能獲取到當(dāng)前線程的副本值,形成了線程的隔離,互不干擾。

3、ThreadLocal 的核心方法:

ThreadLocal 對(duì)外暴露的方法有4個(gè):

1.initialValue()方法:返回為當(dāng)前線程初始副本變量值。
2.get()方法:獲取當(dāng)前線程的副本變量值。
3.set()方法:保存當(dāng)前線程的副本變量值。
4.remove()方法:移除當(dāng)前前程的副本變量值

 1、set()方法:

// 設(shè)置當(dāng)前線程對(duì)應(yīng)的ThreadLocal值
public void set(T value) {
    Thread t = Thread.currentThread(); // 獲取當(dāng)前線程對(duì)象
    ThreadLocalMap map = getMap(t);
    if (map != null) // 判斷map是否存在
        map.set(this, value); 
        // 調(diào)用map.set 將當(dāng)前value賦值給當(dāng)前threadLocal。
    else
        createMap(t, value);
        // 如果當(dāng)前對(duì)象沒有ThreadLocalMap 對(duì)象。
        // 創(chuàng)建一個(gè)對(duì)象 賦值給當(dāng)前線程
}
 
// 獲取當(dāng)前線程對(duì)象維護(hù)的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
// 給傳入的線程 配置一個(gè)threadlocals
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

執(zhí)行流程:

1.獲得當(dāng)前線程,根據(jù)當(dāng)前線程獲得 map。
2.如果 map 不為空,則將參數(shù)設(shè)置到 map 中,當(dāng)前的 Threadlocal 作為 key。
3.如果 map 為空,則給該線程創(chuàng)建 map,設(shè)置初始值。

 2、get()方法:

public T get() {
    Thread t = Thread.currentThread();//獲得當(dāng)前線程對(duì)象
    ThreadLocalMap map = getMap(t);//線程對(duì)象對(duì)應(yīng)的map
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);// 以當(dāng)前threadlocal為key,嘗試獲得實(shí)體
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果當(dāng)前線程對(duì)應(yīng)map不存在
    // 如果map存在但是當(dāng)前threadlocal沒有關(guān)連的entry。
    return setInitialValue();
}
 
// 初始化
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;
}

執(zhí)行流程:

(1)先嘗試獲得當(dāng)前線程,再根據(jù)當(dāng)前線程獲取對(duì)應(yīng)的 map
(2)如果獲得的 map 不為空,以當(dāng)前 threadlocal 為 key 嘗試獲得 entry
(3)如果 entry 不為空,返回值。
(4)如果 2 跟 3 出現(xiàn)無法獲得,則通過 initialValue 函數(shù)獲得初始值,然后給當(dāng)前線程創(chuàng)建新 map

 3、remove()方法:

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

執(zhí)行流程:

首先嘗試獲取當(dāng)前線程,然后根據(jù)當(dāng)前線程獲得map,從map中嘗試刪除enrty。

4、initialValue() 方法:

protected T initialValue() {
    return null;
}

執(zhí)行流程:

(1)如果沒有調(diào)用 set() 直接 get(),則會(huì)調(diào)用此方法,該方法只會(huì)被調(diào)用一次,
(2)默認(rèn)返回一個(gè)缺省值null,如果不想返回null,可以O(shè)verride 進(jìn)行覆蓋。

4、ThreadLocal 的哈希沖突的解決方法:線性探測(cè)

和 HashMap 不同,ThreadLocalMap 結(jié)構(gòu)中沒有 next 引用
ThreadLocalMap 中解決哈希沖突的方式并非鏈表的方式,而是采用線性探測(cè)的方式,當(dāng)發(fā)生哈希沖突時(shí)就將步長(zhǎng)加1或減1,尋找下一個(gè)相鄰的位置

流程說明:

1.根據(jù) ThreadLocal 對(duì)象的 hash 值,定位到 table 中的位置 i;

2.如果當(dāng)前位置是 null,就初始化一個(gè) Entry 對(duì)象放在位置 i 上;

3.如果位置 i 已經(jīng)有 Entry 對(duì)象了,如果這個(gè) Entry 對(duì)象的 key 與即將設(shè)置的 key 相同,那么重新設(shè)置 Entry 的 value;

4.如果位置 i 的 Entry 對(duì)象和 即將設(shè)置的 key 不同,那么尋找下一個(gè)空位置;

5、ThreadLocal 的內(nèi)存泄露:

在使用 ThreadLocal 時(shí),當(dāng)使用完變量后,必須手動(dòng)調(diào)用 remove() 方法刪除 entry 對(duì)象,否則會(huì)造成 value 的內(nèi)存泄露,嚴(yán)格來說,ThreadLocal 是沒有內(nèi)存泄漏問題,有的話,那也是忘記執(zhí)行 remove() 引起的

內(nèi)存泄露的根本原因在于 ThreadLocalMap 的生命周期與當(dāng)前線程 CurrentThread 的生命周期相同,且 ThreadLocal 使用完沒有進(jìn)行手動(dòng)刪除導(dǎo)致的

ThreadLocal 的內(nèi)存泄露與強(qiáng)弱引用無關(guān),那么為什么還要用弱引用呢?

(1)Entry 中的 key(Threadlocal)是弱引用,目的是將 ThreadLocal 對(duì)象的生命周期跟線程周期解綁,用 WeakReference 弱引用關(guān)聯(lián)的對(duì)象,只能生存到下一次垃圾回收之前,GC發(fā)生時(shí),不管內(nèi)存夠不夠,都會(huì)被回收。

(2)當(dāng)我們使用完 ThreadLocal,而 Thread 仍然運(yùn)行時(shí),即使忘記調(diào)用 remove() 方法, 弱引用也會(huì)比強(qiáng)引用多一層保障:當(dāng) GC 發(fā)生時(shí),弱引用的 ThreadLocal 被收回,那么 key 就為 null 了。而 ThreadLocalMap 中的 set()、get() 方法,會(huì)針對(duì) key == null (也就是 ThreadLocal 為 null) 的情況進(jìn)行處理,如果 key == null,則系統(tǒng)認(rèn)為 value 也應(yīng)該是無效了應(yīng)該設(shè)置為 null,也就是說對(duì)應(yīng)的 value 會(huì)在下次調(diào)用 ThreadLocal 的 set()、get() 方法時(shí),執(zhí)行底層 ThreadLocalMap 中的 expungeStaleEntry() 方法進(jìn)行清除無用的 value,從而避免內(nèi)存泄露。

6、ThreadLocal 的應(yīng)用場(chǎng)景:

(1)Hibernate 的 session 獲?。好總€(gè)線程訪問數(shù)據(jù)庫都應(yīng)當(dāng)是一個(gè)獨(dú)立的 session 會(huì)話,如果多個(gè)線程共享同一個(gè) session 會(huì)話,有可能其他線程關(guān)閉連接了,當(dāng)前線程再執(zhí)行提交時(shí)就會(huì)出現(xiàn)會(huì)話已關(guān)閉的異常,導(dǎo)致系統(tǒng)異常。

使用 ThreadLocal 的方式能避免線程爭(zhēng)搶session,提高并發(fā)安全性。

(2)Spring 的事務(wù)管理:事務(wù)需要保證一組操作同時(shí)成功或失敗,意味著一個(gè)事務(wù)的所有操作需要在同一個(gè)數(shù)據(jù)庫連接上,Spring 采用 Threadlocal 的方式,來保證單個(gè)線程中的數(shù)據(jù)庫操作使用的是同一個(gè)數(shù)據(jù)庫連接,同時(shí)采用這種方式可以使業(yè)務(wù)層使用事務(wù)時(shí)不需要感知并管理 connection 對(duì)象,通過傳播級(jí)別,巧妙地管理多個(gè)事務(wù)配置之間的切換,掛起和恢復(fù)

7、如果想共享線程的 ThreadLocal 數(shù)據(jù)怎么辦 ?

使用 InheritableThreadLocal 可以實(shí)現(xiàn)多個(gè)線程訪問 ThreadLocal 的值

我們?cè)谥骶€程中創(chuàng)建一個(gè) InheritableThreadLocal 的實(shí)例,然后在子線程中得到這個(gè)InheritableThreadLocal實(shí)例設(shè)置的值。

private void test() {    
final ThreadLocal threadLocal = new InheritableThreadLocal();       
threadLocal.set("主線程的ThreadLocal的值");    
Thread t = new Thread() {        
    @Override        
    public void run() {            
      super.run();            
      Log.i( "我是子線程,我要獲取其他線程的ThreadLocal的值 ==> " + threadLocal.get());        
    }    
  };          
  t.start(); 
} 

8、為什么一般用 ThreadLocal 都要用 static?

ThreadLocal 能實(shí)現(xiàn)線程的數(shù)據(jù)隔離,不在于它自己本身,而在于 Thread 的 ThreadLocalMap,所以,ThreadLocal 可以只實(shí)例化一次,只分配一塊存儲(chǔ)空間就可以了,沒有必要作為成員變量多次被初始化。

到此這篇關(guān)于Java多線程之ThreadLocal原理總結(jié)的文章就介紹到這了,更多相關(guān)Java多線程ThreadLocal內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論