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

Java 超詳細講解ThreadLocal類的使用

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

Threadlocal有什么用:

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

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

ThreadLocal使用實例

API介紹

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

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

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

ThreadLocal的使用

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

public class ThreadLocalTest {

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

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

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

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

            }
        }).start();

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

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

    }

}

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

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

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

程序結果及分析?

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

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

Threadlocal 的源碼分析

原理

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

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

源碼

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

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

內部類ThreadLocalMap

ThreadLocalMap是ThreadLocal的內部類,實現(xiàn)了一套自己的Map結構?

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;
        //負載因子
        private int threshold; 

ThreadLocalMap設置ThreadLocal 變量

    private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            
            //與運算  & (len-1) 這就是為什么 要求數(shù)組len 要求2的n次冪 
            //因為len減一后最后一個bit是1 與運算計算出來的數(shù)值下標 能保證全覆蓋 
            //否者數(shù)組有效位會減半 
            //如果是hashmap 計算完下標后 會增加鏈表 或紅黑樹的查找計算量 
            int i = key.threadLocalHashCode & (len-1);
            
            // 從下標位置開始向后循環(huán)搜索  不會死循環(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;
                }
                //槽點被GC掉 重設狀態(tài) 
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//槽點為空 設置value
            tab[i] = new Entry(key, value);
            //設置ThreadLocal數(shù)量
            int sz = ++size;
			
			//沒有可清理的槽點 并且數(shù)量大于負載因子 rehash
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

ThreadLocalMap屬性介紹??:

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

ThreadLocalMap存儲位置

儲存在Thread中,有兩個ThreadLocalMap變量

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

threadLocals 在ThreadLocal對象方法set中去創(chuàng)建 也由ThreadLocal來維護

    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重寫了createMap方法

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

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

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

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

在這里插入圖片描述

注意:

  • 僅在初始化子線程的時候會傳遞 中途改變副線程的inheritableThreadLocals 變量 不會將影響結果傳遞到子線程 。
  • 使用線程池要注意 線程不回收 盡量避免使用父線程的inheritableThreadLocals 導致錯誤

Key的弱引用問題

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

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

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

生命周期長的線程可以理解為:線程池的核心線程

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

  • key 使用強引用??: 引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏。
  • key 使用弱引用??: 引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。

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

java中的四種引用

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

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

總結:

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

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

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

相關文章

  • SpringMVC中的常用注解和用法舉例詳解

    SpringMVC中的常用注解和用法舉例詳解

    在Spring和SpringMVC中,注解的使用大大簡化了配置,提高了代碼的可讀性和可維護性,這篇文章主要給大家介紹了關于SpringMVC中常用注解和用法的相關資料,需要的朋友可以參考下
    2024-08-08
  • Java實現(xiàn)隨機生成大小寫混合的卡密的步驟

    Java實現(xiàn)隨機生成大小寫混合的卡密的步驟

    在現(xiàn)代軟件開發(fā)中,生成隨機卡密是一個常見的需求,尤其是在需要為用戶生成唯一識別碼或安全令牌的場景中,卡密通常由數(shù)字和字母組成,有時還會包含特殊字符,本文通過代碼講解的非常詳細,需要的朋友可以參考下
    2024-11-11
  • java定時任務實現(xiàn)的4種方式小結

    java定時任務實現(xiàn)的4種方式小結

    這篇文章主要介紹了java定時任務實現(xiàn)的4種方式小結,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java中獲取List中最后一個元素的三種方法

    Java中獲取List中最后一個元素的三種方法

    在Java編程中我們經(jīng)常需要獲取一個List集合中的最后一個元素,這篇文章主要給大家介紹了關于Java中獲取List中最后一個元素的三種方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2023-12-12
  • C/C++和Java的交互詳解

    C/C++和Java的交互詳解

    本文旨在簡單的介紹一下如何讓java層和C/C++層相互調用,這里主要是使用了JNI技術,并沒有深究其原理,只是做了個實現(xiàn),其目的是為后面的學習打開一扇窗。
    2016-01-01
  • Gateway集成Netty服務的配置加載詳解

    Gateway集成Netty服務的配置加載詳解

    這篇文章主要為大家介紹了Gateway集成Netty服務的配置加載詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • Java 數(shù)據(jù)流之Broadcast State

    Java 數(shù)據(jù)流之Broadcast State

    這篇文章主要介紹了Java 數(shù)據(jù)流之Broadcast State,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • java中類和對象的知識點總結

    java中類和對象的知識點總結

    在本篇文章里小編給大家整理了一篇關于java中類和對象的知識點總結,有需要的朋友們可以學習下。
    2020-12-12
  • Java過濾器filter_動力節(jié)點Java學院整理

    Java過濾器filter_動力節(jié)點Java學院整理

    這篇文章主要介紹了Java過濾器filter,通過過濾器,可以對來自客戶端的請求進行攔截,進行預處理或者對最終響應給客戶端的數(shù)據(jù)進行處理后再輸出
    2017-07-07
  • String類的獲取功能、轉換功能

    String類的獲取功能、轉換功能

    這篇文章給大家介紹了String類的獲取功能:String類的基本獲取功能、獲取功能的舉例子、String類的基本轉換功能、轉換功能的舉例子。具體詳情大家參考下本文
    2018-04-04

最新評論

南通市| 吴堡县| 介休市| 汾西县| 金门县| 木里| 得荣县| 乃东县| 武清区| 五家渠市| 敖汉旗| 汾西县| 那曲县| 长葛市| 铁力市| 剑河县| 禄丰县| 虹口区| 武邑县| 灵寿县| 武山县| 平南县| 重庆市| 敦化市| 黄陵县| 木兰县| 寻乌县| 沽源县| 玉龙| 西吉县| 兴化市| 宁安市| 沙洋县| 溧水县| 枣强县| 沁水县| 浪卡子县| 溆浦县| 温州市| 克什克腾旗| 吴桥县|