中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中ThreadLocal類怎么使用

發布時間:2022-04-08 09:15:27 來源:億速云 閱讀:148 作者:iii 欄目:開發技術

這篇文章主要介紹“Java中ThreadLocal類怎么使用”,在日常操作中,相信很多人在Java中ThreadLocal類怎么使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中ThreadLocal類怎么使用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    Threadlocal有什么用:

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

    Java中ThreadLocal類怎么使用

    ThreadLocal使用實例

    API介紹

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

    Java中ThreadLocal類怎么使用

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

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

    程序結果及分析?

    Java中ThreadLocal類怎么使用

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

    Threadlocal 的源碼分析

    原理

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

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

    源碼

    Java中ThreadLocal類怎么使用

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

    內部類ThreadLocalMap

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

    ThreadLocalMap屬性介紹????:

    • 和普通Hashmap類似存儲在一個數組內,但與hashmap使用的拉鏈法解決散列沖突不同的是 ThreadLocalMap使用開放地址法

    • 數組 初始容量16,負載因子2/3

    • node節點 的key封裝了WeakReference 用于回收

    ThreadLocalMap存儲位置

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

    Java中ThreadLocal類怎么使用

    threadLocals 在ThreadLocal對象方法set中去創建 也由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傳遞給子線程

    Java中ThreadLocal類怎么使用

    init方法中 條件滿足后直接為子線程創建ThreadLocalMap

    Java中ThreadLocal類怎么使用

    注意:

    • 僅在初始化子線程的時候會傳遞 中途改變副線程的inheritableThreadLocals 變量 不會將影響結果傳遞到子線程 。

    • 使用線程池要注意 線程不回收 盡量避免使用父線程的inheritableThreadLocals 導致錯誤

    Key的弱引用問題

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

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

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

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

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

    • key 使用強引用????: 引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏。

    • key 使用弱引用????: 引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。

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

    java中的四種引用

    • 強引用????: 如果一個對象具有強引用,它就不會被垃圾回收器回收。即使當前內存空間不足,JVM也不會回收它,而是拋出 OutOfMemoryError 錯誤,使程序異常終止。如果想中斷強引用和某個對象之間的關聯,可以顯式地將引用賦值為null,這樣一來的話,JVM在合適的時間就會回收該對象

    • 軟引用????: 在使用軟引用時,如果內存的空間足夠,軟引用就能繼續被使用,而不會被垃圾回收器回收,只有在內存不足時,軟引用才會被垃圾回收器回收。(軟引用可用來實現內存敏感的高速緩存,比如網頁緩存、圖片緩存等。使用軟引用能防止內存泄露,增強程序的健壯性)

    • 弱引用????: 具有弱引用的對象擁有的生命周期更短暫。因為當 JVM 進行垃圾回收,一旦發現弱引用對象,無論當前內存空間是否充足,都會將弱引用回收。不過由于垃圾回收器是一個優先級較低的線程,所以并不一定能迅速發現弱引用對象

    • 虛引用????: 虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。(注意哦,其它引用是被JVM回收后才被傳入ReferenceQueue中的。由于這個機制,所以虛引用大多被用于引用銷毀前的處理工作。可以使用在對象銷毀前的一些操作,比如說資源釋放等。)

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

    到此,關于“Java中ThreadLocal類怎么使用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    武鸣县| 奉节县| 阳原县| 霍山县| 依兰县| 凌海市| 江安县| 绥棱县| 扶风县| 江西省| 西和县| 云霄县| 洛浦县| 宜昌市| 贵州省| 宁城县| 大庆市| 临海市| 郴州市| 郁南县| 宁乡县| 大英县| 黔东| 左贡县| 久治县| 高台县| 西丰县| 徐汇区| 西藏| 边坝县| 乐清市| 滦平县| 嘉义县| 永定县| 曲水县| 紫金县| 报价| 昭通市| 卫辉市| 潞西市| 和田县|