最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

ThreadLocal常用方法、使用場景及注意事項說明

 更新時間:2021年10月12日 09:36:34   作者:一碼事  
這篇文章主要介紹了ThreadLocal常用方法、使用場景及注意事項說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

1. ThreadLocal詳解

JDK1.2版本起,Java就提供了java.lang.ThreadLocal,ThreadLocal為每個使用線程都提供獨立的變量副本,可以做到線程間的數(shù)據(jù)隔離,每個線程都可以訪問各自內(nèi)部的副本變量。

線程上下文ThreadLocal又稱為"線程保險箱",ThreadLocal能夠?qū)⒅付ǖ淖兞亢彤?dāng)前線程進(jìn)行綁定,線程之間彼此隔離,持有不同的對象實例,從而避免了數(shù)據(jù)資源的競爭。

2. ThreadLocal的使用場景

  • 在進(jìn)行對象跨層傳遞的時候,可以考慮ThreadLocal,避免方法多次傳遞,打破層次間的約束。
  • 線程間數(shù)據(jù)隔離。
  • 進(jìn)行事務(wù)操作,用于儲存線程事務(wù)信息。

注意:

ThreadLocal并不是解決多線程下共享資源的一種技術(shù),一般情況下,每一個線程的ThreadLocal存儲的都是一個全新的對象(通過new關(guān)鍵字創(chuàng)建),如果多線程的ThreadLocal存儲了一個對象引用,那么就會面臨資源競爭,數(shù)據(jù)不一致等并發(fā)問題。

3.常用方法源碼解析

3.1 initialValue方法

 protected T initialValue() {
        return null;
 }

此方法為ThreadLocal保存的數(shù)據(jù)類型指定的一個初始化值,在ThreadLocal中默認(rèn)返回null。但可以重寫initialValue()方法進(jìn)行數(shù)據(jù)初始化。

如果使用的是Java8提供的Supplier函數(shù)接口更加簡化:

// withInitial()實際是創(chuàng)建了一個ThreadLocal的子類SuppliedThreadLocal,重寫initialValue()
ThreadLocal<Object> threadLocal = ThreadLocal.withInitial(Object::new);

3.2 set(T value)方法

主要存儲指定數(shù)據(jù)。

public void set(T value) {
    // 獲取當(dāng)前線程Thread.currentThread() 
    Thread t = Thread.currentThread();
    // 根據(jù)當(dāng)前線程獲取與之關(guān)聯(lián)的ThreadLocalMap數(shù)據(jù)結(jié)構(gòu)
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 核心方法。set 遍歷整個Entry的過程,后面有詳解
        map.set(this, value);
    else {
        // 調(diào)用createMap(),創(chuàng)建ThreadLocalMap,key為當(dāng)前ThreadLocal實例,存入數(shù)據(jù)為當(dāng)前value。
        // ThreadLocal會創(chuàng)建一個默認(rèn)長度為16Entry節(jié)點,并將k-v放入i位置(i位置計算方式和hashmap相似,
        // 當(dāng)前線程的hashCode&(entry默認(rèn)長度-1)),并設(shè)置閾值(默認(rèn)為0)為Entry默認(rèn)長度的2/3。
        createMap(t, value);
    }
}
// set 遍歷整個Entry的過程
private void set(ThreadLocal<?> key, Object value) {
    // 獲取所有的Entry
    Entry[] tab = table;
    int len = tab.length;
    // 根據(jù)ThreadLocal對象,計算角標(biāo)位置
    int i = key.threadLocalHashCode & (len-1);
 // 循環(huán)查找
    for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
  // 找到相同的就直接覆蓋,直接返回。
        if (k == key) {
            e.value = value;
            return;
        }
  // 如果ThreadLocal為null,直接驅(qū)出并使用新數(shù)據(jù)(Value)占居原來位置,
  // 這個過程主要是防止內(nèi)存泄漏。
        if (k == null) {
            // 驅(qū)除ThreadLocal為null的Entry,并放入Value,這也是內(nèi)存泄漏的重點地區(qū)
            replaceStaleEntry(key, value, i);
            return;
        }
    }
 // entry都為null,創(chuàng)建新的entry,已ThreadLocal為key,將存放數(shù)據(jù)為Value。
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // ThreadLoaclMapde的當(dāng)前數(shù)據(jù)元素的個數(shù)和閾值比較,再次進(jìn)行key為null的清理工作。
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        // 整理Entry,當(dāng)Entry中的ThreadLocal對象為null時,通過重新計算角標(biāo)位來清理
        // 以前ThreadLocal。如果Entry數(shù)量大于3/4容量進(jìn)行擴(kuò)容
        rehash();
}

3.3 get方法

get()用于返回當(dāng)前線程ThreadLocal中數(shù)據(jù)備份,當(dāng)前線程的數(shù)據(jù)都存在一個ThreadLocalMap的數(shù)據(jù)結(jié)構(gòu)中。

public T get() {
    Thread t = Thread.currentThread();
    // 獲得ThreadLocalMap對象map,ThreadLocalMap是和當(dāng)前Thread關(guān)聯(lián)的,
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 存入ThreadLocal中的數(shù)據(jù)實際上是存儲在ThreadLocalMap的Entry中。
        // 而此Entry是放在一個Entry數(shù)組里面的。
        // 獲取當(dāng)前ThreadLocal對應(yīng)的entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            // 直接返回當(dāng)前數(shù)據(jù) 
            T result = (T)e.value;
            return result;
        }
    }
    // ThreadLocalMap未初始化,首先初始化
    return setInitialValue();
}
// ThreadLocal的setInitialValue方法源碼
private T setInitialValue() {
    // 為ThreadLocalMap指定Value的初始化值
    T value = initialValue();
    Thread t = Thread.currentThread();
    // 根據(jù)本地線程Thread獲取ThreadLocalMap,一下方法與Set方法相同。
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 如果map存在,直接調(diào)用set()方法進(jìn)行賦值。
        map.set(this, value);
    else
        // map==null;創(chuàng)建ThreadLocalMap對象,并將Thread和value關(guān)聯(lián)起來
        createMap(t, value);
    return value;
}

3.4 小結(jié)

  • initialValue():初始化ThreadLocal中的value屬性值。
  • set():獲取當(dāng)前線程,根據(jù)當(dāng)前線程從ThreadLocals中獲取ThreadLocalMap數(shù)據(jù)結(jié)構(gòu),
    • 如果ThreadLocalmap的數(shù)據(jù)結(jié)構(gòu)沒創(chuàng)建,則創(chuàng)建ThreadLocalMap,key為當(dāng)前ThreadLocal實例,存入數(shù)據(jù)為當(dāng)前value。ThreadLocal會創(chuàng)建一個默認(rèn)長度為16Entry節(jié)點,并將k-v放入i位置(i位置計算方式和hashmap相似,當(dāng)前線程的hashCode&(entry默認(rèn)長度-1)),并設(shè)置閾值(默認(rèn)為0)為Entry默認(rèn)長度的2/3。
    • 如果ThreadLocalMap存在。就會遍歷整個Map中的Entry節(jié)點,如果entry中的key和本線程ThreadLocal相同,將數(shù)據(jù)(value)直接覆蓋,并返回。如果ThreadLoca為null,驅(qū)除ThreadLocal為null的Entry,并放入Value,這也是內(nèi)存泄漏的重點地區(qū)。
  • get()
  • get()方法比較簡單。就是根據(jù)Thread獲取ThreadLocalMap。通過ThreadLocal來獲得數(shù)據(jù)value。注意的是:如果ThreadLocalMap沒有創(chuàng)建,直接進(jìn)入創(chuàng)建過程。初始化ThreadLocalMap。并直接調(diào)用和set方法一樣的方法。

3.4 ThreadLocalMap數(shù)據(jù)結(jié)構(gòu)

set()還是get()方法都是避免不了和ThreadLocalMap和Entry打交道。ThreadLocalMap是一個類似于HashMap的一個數(shù)據(jù)結(jié)構(gòu)(沒有鏈表),僅僅用于存放線程存放在ThreadLocal中的數(shù)據(jù)備份,ThreadLocalMap的所有方法對外部都是不可見的。

ThreadLocalMap中用于存儲數(shù)據(jù)的Entry,它是一個WeakReference類型的子類,之所以設(shè)計成WeakReference是為了能夠是JVM發(fā)生gc,能夠自動回收,防止內(nèi)存溢出現(xiàn)象。

4. ThreadLocal的副作用

4.1 ThreadLocal引起臟數(shù)據(jù)

線程復(fù)用會產(chǎn)生臟數(shù)據(jù)。

由于結(jié)程池會重用 Thread 對象 ,那么與 Thread 綁定的類的靜態(tài)屬性 ThreadLocal 變量也會被重用。如果在實現(xiàn)的線程 run()方法體中不顯式地調(diào)用 remove() 清理與線程相關(guān)的ThreadLocal 信息,那么如果下一個線程不調(diào)用set()設(shè)置初始值,就可能 get()到重用的線程信息,包括 ThreadLocal 所關(guān)聯(lián)的線程對象的 value 值。

// java.lang.Thread#threadLocals
  /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
  ThreadLocal.ThreadLocalMap threadLocals = null;

4.2 ThreadLocal引起的內(nèi)存泄漏

在上面提到ThreadLocalMap中存放的Entry是WeakReference的子類。所以在JVM觸發(fā)GC(young gc,F(xiàn)ull GC)時,都會導(dǎo)致Entry的回收

在get數(shù)據(jù)的時候,增加檢查,清除已經(jīng)被回收器回收的Entry(WeakReference可以自動回收)

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    ThreadLocal<?> k = e.get();
  ...
    if (k == null)
        // 清除 key 是 null 的Entry
        expungeStaleEntry(i);
  ...
 return null;
}
private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            // 清除key==null 的Entry
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}

set數(shù)據(jù)時增加檢查,刪除已經(jīng)被垃圾回收器清理的Entry,并將其移除

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            // 清除key==null 的Entry
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}

基于上面三點:ThreadLocal在一定程度上保證不會發(fā)生內(nèi)存泄漏。但是Thread類中有ThreadlocalMap的引用,導(dǎo)致對象的可達(dá)性,故不能回收。

ThreadLocal被置為null清除了。但是通過ThreadLocalMap還是被Thread類引用。導(dǎo)致該數(shù)據(jù)是可達(dá)的。所以內(nèi)存得不到釋放,除非當(dāng)前線程結(jié)束,Thread引用就會被垃圾回收器回收。如圖所示

ThreadLocal對象的引用.jpg

ThreadLocal中的Ref對象為null時的引用鏈5

5. ThreadLocal內(nèi)存泄漏解決方案及remove方法源碼解析

解決ThreadLocal內(nèi)存泄漏的常用方法是:在使用完ThreadLocal之后,及時remove掉。

public void remove() {
    // 根據(jù)當(dāng)前線程,獲取ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        // map不為null,執(zhí)行remove操作
        m.remove(this);
}
// ThreadLocal 的remove()
private void remove(ThreadLocal<?> key) {
    // 獲取存放key-value的數(shù)組。
    Entry[] tab = table;
    int len = tab.length;
    // 根據(jù)ThreadLocal的HashCode確定唯一的角標(biāo)
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            // 如果和本ThreadLocal相同。將引用置null。
            e.clear();
            // 實行Enty和Entry.value置null。源碼中 tab[staleSlot].value = null; tab[staleSlot] = null;
            expungeStaleEntry(i);
            return;
        }
    }
}

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java內(nèi)存之happens-before和重排序

    Java內(nèi)存之happens-before和重排序

    在JMM(Java內(nèi)存模型)中,如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須存在happens-before關(guān)系。下面小編來簡單介紹一下
    2019-05-05
  • idea2020.3配置maven環(huán)境并配置Tomcat的詳細(xì)教程

    idea2020.3配置maven環(huán)境并配置Tomcat的詳細(xì)教程

    這篇文章主要介紹了idea2020.3配置maven環(huán)境并配置Tomcat的詳細(xì)教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • Java中ArrayList集合的常用方法大全

    Java中ArrayList集合的常用方法大全

    這篇文章主要給大家介紹了關(guān)于Java中ArrayList集合的常用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • SpringBoot使用protobuf格式的接口方式

    SpringBoot使用protobuf格式的接口方式

    這篇文章主要介紹了SpringBoot使用protobuf格式的接口方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • springboot2整合redis使用lettuce連接池的方法(解決lettuce連接池?zé)o效問題)

    springboot2整合redis使用lettuce連接池的方法(解決lettuce連接池?zé)o效問題)

    這篇文章主要介紹了springboot2整合redis使用lettuce連接池(解決lettuce連接池?zé)o效問題),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • 使用Spring?Batch實現(xiàn)大數(shù)據(jù)處理的操作方法

    使用Spring?Batch實現(xiàn)大數(shù)據(jù)處理的操作方法

    通過使用Spring?Batch,我們可以高效地處理大規(guī)模數(shù)據(jù),本文介紹了如何配置和實現(xiàn)一個基本的Spring?Batch作業(yè),包括讀取數(shù)據(jù)、處理數(shù)據(jù)和寫入數(shù)據(jù)的全過程,感興趣的朋友跟隨小編一起看看吧
    2024-07-07
  • Spark學(xué)習(xí)筆記 (二)Spark2.3 HA集群的分布式安裝圖文詳解

    Spark學(xué)習(xí)筆記 (二)Spark2.3 HA集群的分布式安裝圖文詳解

    這篇文章主要介紹了Spark2.3 HA集群的分布式安裝,結(jié)合圖文與實例形式詳細(xì)分析了Spark2.3 HA集群分布式安裝具體下載、安裝、配置、啟動及執(zhí)行spark程序等相關(guān)操作技巧,需要的朋友可以參考下
    2020-02-02
  • java文件復(fù)制代碼片斷(java實現(xiàn)文件拷貝)

    java文件復(fù)制代碼片斷(java實現(xiàn)文件拷貝)

    本文介紹java實現(xiàn)文件拷貝的代碼片斷,大家可以直接放到程序里運(yùn)行
    2014-01-01
  • SVN報錯:Error Updating changes:svn:E155037的解決方案

    SVN報錯:Error Updating changes:svn:E155037的解決方案

    今天小編就為大家分享一篇關(guān)于SVN報錯:Error Updating changes:svn:E155037的解決方案,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • spring boot中的條件裝配bean的實現(xiàn)

    spring boot中的條件裝配bean的實現(xiàn)

    這篇文章主要介紹了spring boot中的條件裝配bean的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12

最新評論

栾川县| 绿春县| 类乌齐县| 承德市| 沛县| 乐山市| 浦江县| 深圳市| 抚宁县| 普陀区| 棋牌| 大丰市| 富宁县| 冕宁县| 西城区| 绍兴市| 大庆市| 梧州市| 泰宁县| 东城区| 盐亭县| 华池县| 柳州市| 桐梓县| 青海省| 古浪县| 巫溪县| 阿拉善左旗| 修水县| 大庆市| 马公市| 龙川县| 襄垣县| 玉门市| 北安市| 新化县| 高陵县| 大丰市| 白银市| 克山县| 开江县|