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

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

 更新時(shí)間:2023年08月14日 10:25:11   作者:碼農(nóng)BookSea  
這篇文章主要介紹了Java中的線程私有變量ThreadLocal詳解,ThreadLoalMap是ThreadLocal中的一個(gè)靜態(tài)內(nèi)部類,類似HashMap的數(shù)據(jù)結(jié)構(gòu),但并沒有實(shí)現(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ù)兩個(gè) 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 時(shí)才創(chuàng)建它們。ThreadLocalMap可以理解為線程私有的HashMap。

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

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

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

ThreadLocal 原理

set()方法

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

ThreadLocal 的 set() 方法中,會(huì)進(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í)需要繼承該函數(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()方法

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

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;
                }
            }
}

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

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

在執(zhí)行 ThreadLocal 的 set、remove、rehash 等方法時(shí),它都會(huì)掃描 key 為 null 的 Entry,如果發(fā)現(xiàn)某個(gè) Entry 的 key 為 null,則代表它所對應(yīng)的 value 也沒有作用了,所以它就會(huì)把對應(yīng)的 value 置為 null,這樣,value 對象就可以被正?;厥樟恕5羌僭O(shè) ThreadLocal 已經(jīng)不被使用了,那么實(shí)際上 set、remove、rehash 方法也不會(huì)被調(diào)用,與此同時(shí),如果這個(gè)線程又一直存活、不終止的話,那么剛才的那個(gè)調(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 這個(gè)數(shù)字被稱為斐波那契數(shù) 也叫 黃金分割數(shù),帶來的好處就是 hash 分布非常均勻

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

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

ThreadLocal 1.7和1.8的區(qū)別

ThreadLocal 1.7版本的時(shí)候,entry對象的key是Thread。

1.8版本entry的key是ThreadLocal。

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

ThreadLocal 的問題

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

在 ThreadLocalMap 中的 Entry 的 key 是對 ThreadLocal 的 WeakReference 弱引用,而 value 是強(qiáng)引用。

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

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

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

/**
 * 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 后最好手動(dòng)調(diào)用 remove() 方法。但其實(shí)在 ThreadLocalMap 的實(shí)現(xiàn)中以及考慮到這種情況,因此在調(diào)用 set() 、 get() remove() 方法時(shí),會(huì)清理 key 為 null 的記錄。

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

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

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

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

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

ThreadLocal 父子線程繼承

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

它的原理就是子線程是通過在父線程中調(diào)用 new Thread() 創(chuàng)建的,在 Thread 的構(gòu)造方法中調(diào)用了 Thread的init 方法,在 init 方法中父線程數(shù)據(jù)會(huì)復(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();
    }
}

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

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

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

相關(guān)文章

  • springboot打包不同環(huán)境配置以及shell腳本部署的方法

    springboot打包不同環(huán)境配置以及shell腳本部署的方法

    這篇文章主要給大家介紹了關(guān)于springboot打包不同環(huán)境配置以及shell腳本部署的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用springboot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • RabbitMQ之消息的可靠性方案詳解

    RabbitMQ之消息的可靠性方案詳解

    這篇文章主要介紹了RabbitMQ之消息的可靠性方案詳解,MQ 消息數(shù)據(jù)完整的鏈路為:從 Producer 發(fā)送消息到 RabbitMQ 服務(wù)器中,再由 Broker 服務(wù)的 Exchange 根據(jù) Routing_Key 路由到指定的 Queue 隊(duì)列中,最后投送到消費(fèi)者中完成消費(fèi),需要的朋友可以參考下
    2023-08-08
  • 詳解Spring Data JPA動(dòng)態(tài)條件查詢的寫法

    詳解Spring Data JPA動(dòng)態(tài)條件查詢的寫法

    本篇文章主要介紹了Spring Data JPA動(dòng)態(tài)條件查詢的寫法 ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • mybatis-plus中l(wèi)ambdaQuery()與lambdaUpdate()比較常見的使用方法總結(jié)

    mybatis-plus中l(wèi)ambdaQuery()與lambdaUpdate()比較常見的使用方法總結(jié)

    mybatis-plus是在mybatis的基礎(chǔ)上做增強(qiáng)不做改變,簡化了CRUD操作,下面這篇文章主要給大家介紹了關(guān)于mybatis-plus中l(wèi)ambdaQuery()與lambdaUpdate()比較常見的使用方法,需要的朋友可以參考下
    2022-09-09
  • Java封裝數(shù)組之動(dòng)態(tài)數(shù)組實(shí)現(xiàn)方法詳解

    Java封裝數(shù)組之動(dòng)態(tài)數(shù)組實(shí)現(xiàn)方法詳解

    這篇文章主要介紹了Java封裝數(shù)組之動(dòng)態(tài)數(shù)組實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了java動(dòng)態(tài)數(shù)組的實(shí)現(xiàn)原理、操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2020-03-03
  • springcloud項(xiàng)目快速開始起始模板的實(shí)現(xiàn)

    springcloud項(xiàng)目快速開始起始模板的實(shí)現(xiàn)

    本文主要介紹了springcloud項(xiàng)目快速開始起始模板思路的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • 自帶IDEA插件的阿里開源診斷神器Arthas線上項(xiàng)目BUG調(diào)試

    自帶IDEA插件的阿里開源診斷神器Arthas線上項(xiàng)目BUG調(diào)試

    這篇文章主要為大家介紹了自帶IDEA插件阿里開源診斷神器Arthas線上項(xiàng)目BUG調(diào)試,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • SpringMVC自定義攔截器實(shí)現(xiàn)過程詳解

    SpringMVC自定義攔截器實(shí)現(xiàn)過程詳解

    這篇文章主要介紹了SpringMVC自定義攔截器實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • 一文詳解Java如何優(yōu)雅地判斷對象是否為空

    一文詳解Java如何優(yōu)雅地判斷對象是否為空

    這篇文章主要給大家介紹了關(guān)于Java如何優(yōu)雅地判斷對象是否為空的相關(guān)資料,在Java中可以使用以下方法優(yōu)雅地判斷一個(gè)對象是否為空,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-04-04
  • java基礎(chǔ)篇之Date類型最常用的時(shí)間計(jì)算(相當(dāng)全面)

    java基礎(chǔ)篇之Date類型最常用的時(shí)間計(jì)算(相當(dāng)全面)

    這篇文章主要給大家介紹了關(guān)于java基礎(chǔ)篇之Date類型最常用的時(shí)間計(jì)算的相關(guān)資料,Java中的Date類是用來表示日期和時(shí)間的類,它提供了一些常用的方法來處理日期和時(shí)間的操作,需要的朋友可以參考下
    2023-12-12

最新評論