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

Java中的線程私有變量ThreadLocal詳解

 更新時間:2023年08月14日 10:25:11   作者:碼農(nóng)BookSea  
這篇文章主要介紹了Java中的線程私有變量ThreadLocal詳解,ThreadLoalMap是ThreadLocal中的一個靜態(tài)內(nèi)部類,類似HashMap的數(shù)據(jù)結(jié)構(gòu),但并沒有實現(xiàn)Map接口,需要的朋友可以參考下

什么是ThreadLocal

首先看下ThreadLocal的使用示例:

public class ThreadLocalTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            threadLocal.set("本地變量1");
            print("thread1");
            System.out.println("線程1的本地變量的值為:"+threadLocal.get());
        });
        Thread thread2 = new Thread(() -> {
            threadLocal.set("本地變量2");
            print("thread2");
            System.out.println("線程2的本地變量的值為:"+threadLocal.get());
        });
        thread1.start();
        thread2.start();
    }
    public static void print(String s){
        System.out.println(s+":"+threadLocal.get());
    }

執(zhí)行結(jié)果如下

我們從 Thread 類講起,在 Thread 類中有維護(hù)兩個 ThreadLocal.ThreadLocalMap 對象,分別是: threadLocals inheritableThreadLocals 。

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

初始它們都為 null,只有在調(diào)用 ThreadLocal 類的 set 或 get 時才創(chuàng)建它們。ThreadLocalMap可以理解為線程私有的HashMap。

ThreadLoalMap是ThreadLocal中的一個靜態(tài)內(nèi)部類,類似HashMap的數(shù)據(jù)結(jié)構(gòu),但并沒有實現(xiàn)Map接口。

ThreadLoalMap中初始化了一個大小16的Entry數(shù)組,Entry對象用來保存每一個key-value鍵值對。key是ThreadLocal對象。

Entry用來保存數(shù)據(jù) ,而且還是繼承的弱引用。在Entry內(nèi)部使用ThreadLocal作為key,使用我們設(shè)置的value作為value。

ThreadLocal 原理

set()方法

當(dāng)我們調(diào)用 ThreadLocal 的 set() 方法時實際是調(diào)用了當(dāng)前線程的 ThreadLocalMap 的 set() 方法。

ThreadLocal 的 set() 方法中,會進(jìn)一步調(diào)用 Thread.currentThread() 獲得當(dāng)前線程對象 ,然后獲取到當(dāng)前線程對象的ThreadLocal,判斷是不是為空,為空就先調(diào)用 creadMap() 創(chuàng)建再 set(value) 創(chuàng)建 ThreadLocalMap 對象并添加變量。不為空就直接 set(value) 。

這種保證線程安全的方式稱為 線程封閉 。線程只能看到自己的ThreadLocal變量。線程之間是互相隔離的。

get()方法

其中get()方法用來獲取與當(dāng)前線程關(guān)聯(lián)的ThreadLocal的值,如果當(dāng)前線程沒有該ThreadLocal的值,則調(diào)用initialValue函數(shù)獲取初始值返回

所以一般我們使用時需要繼承該函數(shù),給出初始值(不重寫的話默認(rèn)返回Null)。

主要有以下幾步:

  1. 獲取當(dāng)前的Thread對象,通過getMap獲取Thread內(nèi)的ThreadLocalMap
  2. 如果map已經(jīng)存在,以當(dāng)前的ThreadLocal為鍵,獲取Entry對象,并從從Entry中取出值
  3. 否則,調(diào)用setInitialValue進(jìn)行初始化。
/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

我們可以重寫 initialValue() ,設(shè)置初始值。

    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return Integer.valueOf(0);
        }
    }

remove()方法

最后一個需要探究的就是remove方法,它用于在map中移除一個不用的Entry。也是先計算出hash值,若是第一次沒有命中,就循環(huán)直到null,在此過程中也會調(diào)用expungeStaleEntry清除空key節(jié)點。代碼如下:

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
}
/**
 * Remove the entry for key.
 */
private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
}

實際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點是,如果這個對象只存在弱引用,那么在下一次垃圾回收的時候必然會被清理掉。

所以如果 ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候會被清理掉的,這樣一來 ThreadLocalMap中使用這個 ThreadLocal 的 key 也會被清理掉。但是,value 是強引用,不會被清理,這樣一來就會出現(xiàn) key 為 null 的 value。出現(xiàn)內(nèi)存泄漏的問題。

在執(zhí)行 ThreadLocal 的 set、remove、rehash 等方法時,它都會掃描 key 為 null 的 Entry,如果發(fā)現(xiàn)某個 Entry 的 key 為 null,則代表它所對應(yīng)的 value 也沒有作用了,所以它就會把對應(yīng)的 value 置為 null,這樣,value 對象就可以被正常回收了。但是假設(shè) ThreadLocal 已經(jīng)不被使用了,那么實際上 set、remove、rehash 方法也不會被調(diào)用,與此同時,如果這個線程又一直存活、不終止的話,那么剛才的那個調(diào)用鏈就一直存在,也就導(dǎo)致了 value 的內(nèi)存泄漏。

ThreadLocal 的Hash算法

ThreadLocalMap 類似HashMap,它有自己的Hash算法。

private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
  return nextHashCode.getAndAdd(HASH_INCREMENT);
}
public final int getAndAdd(int delta) {
  return unsafe.getAndAddInt(this, valueOffset, delta);
}

HASH_INCREMENT 這個數(shù)字被稱為斐波那契數(shù) 也叫 黃金分割數(shù),帶來的好處就是 hash 分布非常均勻。

每當(dāng)創(chuàng)建一個 ThreadLocal 對象,這個 ThreadLocal.nextHashCode 這個值就會增長 0x61c88647 。

講到Hash就會涉及到Hash沖突,跟HashMap通過鏈地址法不同的是,ThreadLocal是通過線性探測法/開放地址法來解決hash沖突。

ThreadLocal 1.7和1.8的區(qū)別

ThreadLocal 1.7版本的時候,entry對象的key是Thread。

1.8版本entry的key是ThreadLocal。

1.8版本的好處 :當(dāng)Thread銷毀的時候,ThreadLocalMap也會隨之銷毀,減少內(nèi)存的使用。因為ThreadLocalMap是在Thread里面的,所以只要Thread消失了,那ThreadLocalMap就不復(fù)存在了。

ThreadLocal 的問題

ThreadLocal 內(nèi)存泄露問題

在 ThreadLocalMap 中的 Entry 的 key 是對 ThreadLocal 的 WeakReference 弱引用,而 value 是強引用。

當(dāng) ThreadLocalMap 的某 ThreadLocal 對象只被弱引用,GC 發(fā)生時該對象會被清理,此時 key 為 null,但 value 為強引用不會被清理。

此時 value 將訪問不到也不被清理掉就可能會導(dǎo)致內(nèi)存泄漏。

注意構(gòu)造函數(shù)里的第一行代碼super(k),這意味著ThreadLocal對象是一個弱引用

/**
 * The entries in this hash map extend WeakReference, using
 * its main ref field as the key (which is always a
 * ThreadLocal object).  Note that null keys (i.e. entry.get()
 * == null) mean that the key is no longer referenced, so the
 * entry can be expunged from table.  Such entries are referred to
 * as "stale entries" in the code that follows.
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

因此我們使用完 ThreadLocal 后最好手動調(diào)用 remove() 方法。但其實在 ThreadLocalMap 的實現(xiàn)中以及考慮到這種情況,因此在調(diào)用 set() 、 get() 、 remove() 方法時,會清理 key 為 null 的記錄。

為什么采用了弱引用的實現(xiàn)而不是強引用呢?

注釋上有這么一段話:為了協(xié)助處理數(shù)據(jù)比較大并且生命周期比較長的場景,hash table的條目使用了WeakReference作為key。

所以,弱引用反而是為了解決內(nèi)存存儲問題而專門使用的。

實際上,采用弱引用反而多了一層保障,ThreadLocal被清理后key為null,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set、get,就算忘記調(diào)用 remove 方法,弱引用比強引用可以多一層保障。

所以,內(nèi)存泄露的根本原因是是否手動清除操作,而不是弱引用。

ThreadLocal 父子線程繼承

異步場景下無法給子線程共享父線程的線程副本數(shù)據(jù),可以通過 InheritableThreadLocal 類解決這個問題。

它的原理就是子線程是通過在父線程中調(diào)用 new Thread() 創(chuàng)建的,在 Thread 的構(gòu)造方法中調(diào)用了 Thread的init 方法,在 init 方法中父線程數(shù)據(jù)會復(fù)制到子線程( ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); )。

代碼示例:

public class InheritableThreadLocalDemo {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
        threadLocal.set("父類數(shù)據(jù):threadLocal");
        inheritableThreadLocal.set("父類數(shù)據(jù):inheritableThreadLocal");
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子線程獲取父類threadLocal數(shù)據(jù):" + threadLocal.get());
                System.out.println("子線程獲取父類inheritableThreadLocal數(shù)據(jù):" +inheritableThreadLocal.get());
            }
        }).start();
    }
}

但是我們做異步處理都是使用線程池,線程池會復(fù)用線程會導(dǎo)致問題出現(xiàn)。我們可以使用阿里巴巴的TTL解決這個問題。

阿里巴巴的TTL:
https://github.com/alibaba/transmittable-thread-local

到此這篇關(guān)于Java中的線程私有變量ThreadLocal詳解的文章就介紹到這了,更多相關(guān)Java線程ThreadLocal內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺析java消息摘要與數(shù)字簽名

    淺析java消息摘要與數(shù)字簽名

    這篇文章給大家分析了關(guān)于java消息摘要與數(shù)字簽名的相關(guān)知識點內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。
    2018-08-08
  • Springboot整合nacos報錯無法連接nacos的解決

    Springboot整合nacos報錯無法連接nacos的解決

    這篇文章主要介紹了Springboot整合nacos報錯無法連接nacos的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • Java內(nèi)存模型(JMM)開發(fā)應(yīng)用

    Java內(nèi)存模型(JMM)開發(fā)應(yīng)用

    JMM既是一個規(guī)范也是一個工具,正確理解和使用JMM可以幫助我們編寫出既正確又高效的多線程程序,接下來通過本文給大家介紹Java內(nèi)存模型(JMM)開發(fā)應(yīng)用實踐記錄,感興趣的朋友跟隨小編一起看看吧
    2025-11-11
  • Java網(wǎng)絡(luò)編程之簡單的服務(wù)端客戶端應(yīng)用實例

    Java網(wǎng)絡(luò)編程之簡單的服務(wù)端客戶端應(yīng)用實例

    這篇文章主要介紹了Java網(wǎng)絡(luò)編程之簡單的服務(wù)端客戶端應(yīng)用,以實例形式較為詳細(xì)的分析了java網(wǎng)絡(luò)編程的原理與服務(wù)器端客戶端的實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-04-04
  • Java編程將漢字轉(zhuǎn)Unicode碼代碼示例

    Java編程將漢字轉(zhuǎn)Unicode碼代碼示例

    偶然看到Unicode編碼,覺得挺有意思,于是搜索了相關(guān)資料,準(zhǔn)備學(xué)習(xí)學(xué)習(xí),本文主要是一個Unicode編碼的簡單Java實現(xiàn),需要的朋友可以了解下。
    2017-10-10
  • SpringBoot中實現(xiàn)訂單30分鐘自動取消的項目實踐

    SpringBoot中實現(xiàn)訂單30分鐘自動取消的項目實踐

    現(xiàn)在電子商務(wù)平臺上訂單創(chuàng)建成功,等待支付,一般會給30分鐘的時間,本文主要介紹了SpringBoot中實現(xiàn)訂單30分鐘自動取消的項目實踐,具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • spring cloud consul注冊的服務(wù)報錯critical的解決

    spring cloud consul注冊的服務(wù)報錯critical的解決

    這篇文章主要介紹了spring cloud consul注冊的服務(wù)報錯critical的解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • maven倉庫中心mirrors配置多個下載中心(執(zhí)行最快的鏡像)

    maven倉庫中心mirrors配置多個下載中心(執(zhí)行最快的鏡像)

    這篇文章主要介紹了maven倉庫中心mirrors配置多個下載中心(執(zhí)行最快的鏡像),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Java Mybatis框架Dao層的實現(xiàn)與映射文件以及核心配置文件詳解分析

    Java Mybatis框架Dao層的實現(xiàn)與映射文件以及核心配置文件詳解分析

    MyBatis 是一款優(yōu)秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO為數(shù)據(jù)庫中的記錄
    2021-10-10
  • Java 中 Form表單數(shù)據(jù)的兩種提交方式

    Java 中 Form表單數(shù)據(jù)的兩種提交方式

    本文給大家分享java中form表單數(shù)據(jù)的兩種提交方式,分別是get從制定的服務(wù)器中獲取數(shù)據(jù),pos方式提交數(shù)據(jù)給指定的服務(wù)器處理,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2016-12-12

最新評論

塘沽区| 府谷县| 乐山市| 彩票| 宁波市| 加查县| 康平县| 江源县| 梅州市| 蓝山县| 商丘市| 南靖县| 醴陵市| 依安县| 太仓市| 武陟县| 扶余县| 陇西县| 枝江市| 同江市| 新丰县| 合江县| 宁夏| 安吉县| 汉阴县| 涞水县| 松江区| 梅河口市| 平远县| 襄城县| 盖州市| 太康县| 龙泉市| 湟中县| 桂阳县| 葵青区| 新龙县| 建平县| 临清市| 策勒县| 寿阳县|