亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java 超詳細(xì)講解ThreadLocal類的使用

 更新時(shí)間:2022年04月07日 17:50:00   作者:田埂、  
寫(xiě)SpringBoot項(xiàng)目的時(shí)候,經(jīng)常用到的一個(gè)保存用戶信息的類就是Threadlocal,我們今天就來(lái)詳細(xì)介紹一下這個(gè)類,感興趣的朋友來(lái)看看吧

Threadlocal有什么用:

簡(jiǎn)單的說(shuō)就是,一個(gè)ThreadLocal在一個(gè)線程中是共享的,在不同線程之間又是隔離的(每個(gè)線程都只能看到自己線程的值)。如下圖:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-W7WJbbeK-1648006538639)(/assets/2021-10/tl1.png)]

ThreadLocal使用實(shí)例

API介紹

在使用Threadlocal之前我們先看以下它的API:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-G25CvDEY-1648006538641)(/assets/2021-10/tl2.png)]

ThreadLocal類的API非常的簡(jiǎn)單,在這里比較重要的就是get()、set()、remove(),set用于賦值操作,get用于獲取變量的值,remove就是刪除當(dāng)前變量的值.需要注意的是initialValue方法會(huì)在第一次調(diào)用時(shí)被觸發(fā),用于初始化當(dāng)前變量值,默認(rèn)情況下initialValue返回的是null。

ThreadLocal的使用

說(shuō)完了ThreadLocal類的API了,那我們就來(lái)動(dòng)手實(shí)踐一下了,來(lái)理解前面的那句話:一個(gè)ThreadLocal在一個(gè)線程中是共享的,在不同線程之間又是隔離的(每個(gè)線程都只能看到自己線程的值)

public class ThreadLocalTest {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
	// 重寫(xiě)這個(gè)方法,可以修改“線程變量”的初始值,默認(rèn)是null
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) throws InterruptedException {

        //一號(hào)線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("一號(hào)線程set前:" + threadLocal.get());
                threadLocal.set(1);
                System.out.println("一號(hào)線程set后:" + threadLocal.get());
            }
        }).start();

        //二號(hào)線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("二號(hào)線程set前:" + threadLocal.get());
                threadLocal.set(2);
                System.out.println("二號(hào)線程set后:" + threadLocal.get());

            }
        }).start();

        //主線程睡1s
        Thread.sleep(1000);

        //主線程
        System.out.println("主線程的threadlocal值:" + threadLocal.get());

    }

}

稍微解釋一下上面的代碼:

每一個(gè)ThreadLocal實(shí)例就類似于一個(gè)變量名,不同的ThreadLocal實(shí)例就是不同的變量名,它們內(nèi)部會(huì)存有一個(gè)值(暫時(shí)這么理解)在后面的描述中所說(shuō)的“ThreadLocal變量或者是線程變量”代表的就是ThreadLocal類的實(shí)例。

在類中創(chuàng)建了一個(gè)靜態(tài)的 “ThreadLocal變量”,在主線程中創(chuàng)建兩個(gè)線程,在這兩個(gè)線程中分別設(shè)置ThreadLocal變量為1和2。然后等待一號(hào)和二號(hào)線程執(zhí)行完畢后,在主線程中查看ThreadLocal變量的值。

程序結(jié)果及分析?

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-78YHYure-1648006538642)(/assets/2021-10/tl3.png)]

程序結(jié)果重點(diǎn)看的是主線程輸出的是0,如果是一個(gè)普通變量,在一號(hào)線程和二號(hào)線程中將普通變量設(shè)置為1和2,那么在一二號(hào)線程執(zhí)行完畢后在打印這個(gè)變量,輸出的值肯定是1或者2(到底輸出哪一個(gè)由操作系統(tǒng)的線程調(diào)度邏輯有關(guān))。但使用ThreadLocal變量通過(guò)兩個(gè)線程賦值后,在主線程程中輸出的卻是初始值0。在這也就是為什么“一個(gè)ThreadLocal在一個(gè)線程中是共享的,在不同線程之間又是隔離的”,每個(gè)線程都只能看到自己線程的值,這也就是 ThreadLocal的核心作用:實(shí)現(xiàn)線程范圍的局部變量。

Threadlocal 的源碼分析

原理

每個(gè)Thread對(duì)象都有一個(gè)ThreadLocalMap,當(dāng)創(chuàng)建一個(gè)ThreadLocal的時(shí)候,就會(huì)將該ThreadLocal對(duì)象添加到該Map中,其中鍵就是ThreadLocal,值可以是任意類型。 這句話剛看可能不是很懂,下面我們一起看完源碼就明白了。

前面我們的理解是所有的常量值或者是引用類型的引用都是保存在ThreadLocal實(shí)例中的,但實(shí)際上不是的,這種說(shuō)法只是讓我們更好的理解ThreadLocal變量這個(gè)概念。向ThreadLocal存入一個(gè)值,實(shí)際上是向當(dāng)前線程對(duì)象中的ThreadLocalMap存入值,ThreadLocalMap我們可以簡(jiǎn)單的理解成一個(gè)Map,而向這個(gè)Map存值的key就是ThreadLocal實(shí)例本身。

源碼

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-rSZjitt2-1648006538642)(/assets/2021-10/tl4.png)]

??也就是說(shuō),想要存入的ThreadLocal中的數(shù)據(jù)實(shí)際上并沒(méi)有存到ThreadLocal對(duì)象中去,而是以這個(gè)ThreadLocal實(shí)例作為key存到了當(dāng)前線程中的一個(gè)Map中去了,獲取ThreadLocal的值時(shí)同樣也是這個(gè)道理。這也就是為什么ThreadLocal可以實(shí)現(xiàn)線程之間隔離的原因了。

內(nèi)部類ThreadLocalMap

ThreadLocalMap是ThreadLocal的內(nèi)部類,實(shí)現(xiàn)了一套自己的Map結(jié)構(gòu)?

ThreadLocalMap屬性:

        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //初始容量16
        private static final int INITIAL_CAPACITY = 16;
        //散列表
        private Entry[] table;
        //entry 有效數(shù)量 
        private int size = 0;
        //負(fù)載因子
        private int threshold; 

ThreadLocalMap設(shè)置ThreadLocal 變量

    private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            
            //與運(yùn)算  & (len-1) 這就是為什么 要求數(shù)組len 要求2的n次冪 
            //因?yàn)閘en減一后最后一個(gè)bit是1 與運(yùn)算計(jì)算出來(lái)的數(shù)值下標(biāo) 能保證全覆蓋 
            //否者數(shù)組有效位會(huì)減半 
            //如果是hashmap 計(jì)算完下標(biāo)后 會(huì)增加鏈表 或紅黑樹(shù)的查找計(jì)算量 
            int i = key.threadLocalHashCode & (len-1);
            
            // 從下標(biāo)位置開(kāi)始向后循環(huán)搜索  不會(huì)死循環(huán)  有擴(kuò)容因子 必定有空余槽點(diǎn)
            for (Entry e = tab[i];   e != null;  e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //一種情況 是當(dāng)前引用 返回值
                if (k == key) {
                    e.value = value;
                    return;
                }
                //槽點(diǎn)被GC掉 重設(shè)狀態(tài) 
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//槽點(diǎn)為空 設(shè)置value
            tab[i] = new Entry(key, value);
            //設(shè)置ThreadLocal數(shù)量
            int sz = ++size;
			
			//沒(méi)有可清理的槽點(diǎn) 并且數(shù)量大于負(fù)載因子 rehash
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

ThreadLocalMap屬性介紹??:

  • 和普通Hashmap類似存儲(chǔ)在一個(gè)數(shù)組內(nèi),但與hashmap使用的拉鏈法解決散列沖突不同的是 ThreadLocalMap使用開(kāi)放地址法
  • 數(shù)組 初始容量16,負(fù)載因子2/3
  • node節(jié)點(diǎn) 的key封裝了WeakReference 用于回收

ThreadLocalMap存儲(chǔ)位置

儲(chǔ)存在Thread中,有兩個(gè)ThreadLocalMap變量

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-4jrLDowQ-1648006538643)(/assets/2021-10/tl4.5.png)]

threadLocals 在ThreadLocal對(duì)象方法set中去創(chuàng)建 也由ThreadLocal來(lái)維護(hù)

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

inheritableThreadLocals 和ThreadLocal類似 InheritableThreadLocal重寫(xiě)了createMap方法

void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

inheritableThreadLocals 作用是將ThreadLocalMap傳遞給子線程

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-7Lo0mob4-1648006538644)(/assets/2021-10/tl5.png)]

init方法中 條件滿足后直接為子線程創(chuàng)建ThreadLocalMap

在這里插入圖片描述

注意:

  • 僅在初始化子線程的時(shí)候會(huì)傳遞 中途改變副線程的inheritableThreadLocals 變量 不會(huì)將影響結(jié)果傳遞到子線程 。
  • 使用線程池要注意 線程不回收 盡量避免使用父線程的inheritableThreadLocals 導(dǎo)致錯(cuò)誤

Key的弱引用問(wèn)題

為什么要用弱引用,官方是這樣回答的

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.

為了處理非常大和生命周期非常長(zhǎng)的線程,哈希表使用弱引用作為 key。

生命周期長(zhǎng)的線程可以理解為:線程池的核心線程

ThreadLocal在沒(méi)有外部對(duì)象強(qiáng)引用時(shí)如Thread,發(fā)生GC時(shí)弱引用Key會(huì)被回收,而Value是強(qiáng)引用不會(huì)回收,如果創(chuàng)建ThreadLocal的線程一直持續(xù)運(yùn)行如線程池中的線程,那么這個(gè)Entry對(duì)象中的value就有可能一直得不到回收,發(fā)生內(nèi)存泄露。

  • key 使用強(qiáng)引用??: 引用的ThreadLocal的對(duì)象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用,如果沒(méi)有手動(dòng)刪除,ThreadLocal不會(huì)被回收,導(dǎo)致Entry內(nèi)存泄漏。
  • key 使用弱引用??: 引用的ThreadLocal的對(duì)象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒(méi)有手動(dòng)刪除,ThreadLocal也會(huì)被回收。value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。

Java8中已經(jīng)做了一些優(yōu)化如,在ThreadLocal的get()、set()、remove()方法調(diào)用的時(shí)候會(huì)清除掉線程ThreadLocalMap中所有Entry中Key為null的Value,并將整個(gè)Entry設(shè)置為null,利于下次內(nèi)存回收。

java中的四種引用

  • 強(qiáng)引用??: 如果一個(gè)對(duì)象具有強(qiáng)引用,它就不會(huì)被垃圾回收器回收。即使當(dāng)前內(nèi)存空間不足,JVM也不會(huì)回收它,而是拋出 OutOfMemoryError 錯(cuò)誤,使程序異常終止。如果想中斷強(qiáng)引用和某個(gè)對(duì)象之間的關(guān)聯(lián),可以顯式地將引用賦值為null,這樣一來(lái)的話,JVM在合適的時(shí)間就會(huì)回收該對(duì)象
  • 軟引用??: 在使用軟引用時(shí),如果內(nèi)存的空間足夠,軟引用就能繼續(xù)被使用,而不會(huì)被垃圾回收器回收,只有在內(nèi)存不足時(shí),軟引用才會(huì)被垃圾回收器回收。(軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存,比如網(wǎng)頁(yè)緩存、圖片緩存等。使用軟引用能防止內(nèi)存泄露,增強(qiáng)程序的健壯性)
  • 弱引用??: 具有弱引用的對(duì)象擁有的生命周期更短暫。因?yàn)楫?dāng) JVM 進(jìn)行垃圾回收,一旦發(fā)現(xiàn)弱引用對(duì)象,無(wú)論當(dāng)前內(nèi)存空間是否充足,都會(huì)將弱引用回收。不過(guò)由于垃圾回收器是一個(gè)優(yōu)先級(jí)較低的線程,所以并不一定能迅速發(fā)現(xiàn)弱引用對(duì)象
  • 虛引用??: 虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。虛引用必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。(注意哦,其它引用是被JVM回收后才被傳入ReferenceQueue中的。由于這個(gè)機(jī)制,所以虛引用大多被用于引用銷(xiāo)毀前的處理工作。可以使用在對(duì)象銷(xiāo)毀前的一些操作,比如說(shuō)資源釋放等。)

通常ThreadLocalMap的生命周期跟Thread(注意線程池中的Thread)一樣長(zhǎng),如果沒(méi)有手動(dòng)刪除對(duì)應(yīng)key(線程使用結(jié)束歸還給線程池了,其中的KV不再被使用但又不會(huì)GC回收,可認(rèn)為是內(nèi)存泄漏),一定會(huì)導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal會(huì)被GC回收,不會(huì)內(nèi)存泄漏,對(duì)應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除,Java8已經(jīng)做了上面的代碼優(yōu)化。

總結(jié):

ThreadLocal的作用: 實(shí)現(xiàn)線程范圍內(nèi)的局部變量,即ThreadLocal在一個(gè)線程中是共享的,在不同線程之間是隔離的。

ThreadLocal的原理: ThreadLocal存入值時(shí)使用當(dāng)前ThreadLocal實(shí)例作為key(并不是以當(dāng)前線程對(duì)象作為key),存入當(dāng)前線程對(duì)象中的Map中去。最開(kāi)始在看源碼之前,我以為是以當(dāng)前線程對(duì)象作為key將對(duì)象存入到ThreadLocal中的Map中去…

到此這篇關(guān)于Java 超詳細(xì)講解ThreadLocal類的使用的文章就介紹到這了,更多相關(guān)Java ThreadLocal內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java線程阻塞的方法區(qū)別詳解

    Java線程阻塞的方法區(qū)別詳解

    這篇文章主要介紹了Java線程阻塞的方法區(qū)別詳解,線程阻塞是指當(dāng)一個(gè)線程無(wú)法繼續(xù)執(zhí)行時(shí),它會(huì)進(jìn)入阻塞狀態(tài),直到某個(gè)條件滿足后才能繼續(xù)執(zhí)行,線程阻塞可以通過(guò)多種方式實(shí)現(xiàn),如等待鎖、等待IO操作、等待其他線程的完成等,需要的朋友可以參考下
    2023-10-10
  • 在Java8中如何避開(kāi)空指針異常

    在Java8中如何避開(kāi)空指針異常

    這篇文章主要給大家介紹了關(guān)于在Java8中如何風(fēng)騷走位的避開(kāi)空指針異常的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java8具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • 詳解Spring 中 Bean 的生命周期

    詳解Spring 中 Bean 的生命周期

    這篇文章主要介紹了Spring 中 Bean 的生命周期的相關(guān)資料,幫助大家更好的理解和使用spring框架,感興趣的朋友可以了解下。
    2021-01-01
  • 基于Java創(chuàng)建一個(gè)訂單類代碼實(shí)例

    基于Java創(chuàng)建一個(gè)訂單類代碼實(shí)例

    這篇文章主要介紹了基于Java創(chuàng)建一個(gè)訂單類代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • mybatis實(shí)體類字段大小寫(xiě)及字段獲取不到值問(wèn)題

    mybatis實(shí)體類字段大小寫(xiě)及字段獲取不到值問(wèn)題

    這篇文章主要介紹了mybatis實(shí)體類字段大小寫(xiě)及字段獲取不到值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java應(yīng)用EasyExcel工具類

    Java應(yīng)用EasyExcel工具類

    這篇文章主要介紹了Java應(yīng)用EasyExcel工具類,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有一定的幫助,需要的朋友可以參考下
    2021-05-05
  • spring?和?idea?建議不要使用?@Autowired注解的原因解析

    spring?和?idea?建議不要使用?@Autowired注解的原因解析

    @Autowired?是Spring框架的注解,而@Resource是JavaEE的注解,這篇文章主要介紹了spring和idea建議不要使用@Autowired注解的相關(guān)知識(shí),需要的朋友可以參考下
    2023-11-11
  • Java JUnit 使用及常用注解

    Java JUnit 使用及常用注解

    JUnit是Java開(kāi)發(fā)中必不可少的測(cè)試框架之一,它可以幫助您編寫(xiě)高質(zhì)量、可維護(hù)的單元測(cè)試,本文介紹了JUnit的基本用法、常用注解、測(cè)試套件和參數(shù)化測(cè)試等內(nèi)容,希望對(duì)您的測(cè)試工作有所幫助,感興趣的朋友一起看看吧
    2023-12-12
  • Java設(shè)計(jì)模式之原型模式的示例詳解

    Java設(shè)計(jì)模式之原型模式的示例詳解

    原型模式(Prototype Pattern)指使用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。本文將通過(guò)案例詳細(xì)講解一下原型模式,感興趣的可以了解一下
    2022-02-02
  • Spring?WebClient實(shí)戰(zhàn)示例

    Spring?WebClient實(shí)戰(zhàn)示例

    本文主要介紹了Spring?WebClient實(shí)戰(zhàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01

最新評(píng)論