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

溫馨提示×

溫馨提示×

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

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

ThreadLocal如何使用

發布時間:2021-09-06 14:32:55 來源:億速云 閱讀:191 作者:小新 欄目:web開發

這篇文章給大家分享的是有關ThreadLocal如何使用的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

ThreadLocal的基本使用

創建一個ThreadLocal對象:

private ThreadLocal<Integer> localInt = new ThreadLocal<>();

上述代碼創建一個localInt變量,由于ThreadLocal是一個泛型類,這里指定了localInt的類型為整數。

下面展示了如果設置和獲取這個變量的值:

public int setAndGet(){     localInt.set(8);     return localInt.get(); }

上述代碼設置變量的值為8,接著取得這個值。

由于ThreadLocal里設置的值,只有當前線程自己看得見,這意味著你不可能通過其他線程為它初始化值。為了彌補這一點,ThreadLocal提供了一個withInitial()方法統一初始化所有線程的ThreadLocal的值:

private ThreadLocal<Integer> localInt = ThreadLocal.withInitial(() -> 6);

上述代碼將ThreadLocal的初始值設置為6,這對全體線程都是可見的。

ThreadLocal的實現原理

ThreadLocal變量只在單個線程內可見,那它是如何做到的呢?我們先從最基本的get()方法說起:

public T get() {     //獲得當前線程     Thread t = Thread.currentThread();     //每個線程 都有一個自己的ThreadLocalMap,     //ThreadLocalMap里就保存著所有的ThreadLocal變量     ThreadLocalMap map = getMap(t);     if (map != null) {         //ThreadLocalMap的key就是當前ThreadLocal對象實例,         //多個ThreadLocal變量都是放在這個map中的         ThreadLocalMap.Entry e = map.getEntry(this);         if (e != null) {             @SuppressWarnings("unchecked")             //從map里取出來的值就是我們需要的這個ThreadLocal變量             T result = (T)e.value;             return result;         }     }     // 如果map沒有初始化,那么在這里初始化一下     return setInitialValue(); }

可以看到,所謂的ThreadLocal變量就是保存在每個線程的map中的。這個map就是Thread對象中的threadLocals字段。如下:

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap是一個比較特殊的Map,它的每個Entry的key都是一個弱引用:

static class Entry extends WeakReference<ThreadLocal<?>> {     /** The value associated with this ThreadLocal. */     Object value;     //key就是一個弱引用     Entry(ThreadLocal<?> k, Object v) {         super(k);         value = v;     } }

這樣設計的好處是,如果這個變量不再被其他對象使用時,可以自動回收這個ThreadLocal對象,避免可能的內存泄露(注意,Entry中的value,依然是強引用,如何回收,見下文分解)。

理解ThreadLocal中的內存泄漏問題

雖然ThreadLocalMap中的key是弱引用,當不存在外部強引用的時候,就會自動被回收,但是Entry中的value依然是強引用。這個value的引用鏈條如下:

ThreadLocal如何使用

可以看到,只有當Thread被回收時,這個value才有被回收的機會,否則,只要線程不退出,value總是會存在一個強引用。但是,要求每個Thread都會退出,是一個極其苛刻的要求,對于線程池來說,大部分線程會一直存在在系統的整個生命周期內,那樣的話,就會造成value對象出現泄漏的可能。處理的方法是,在ThreadLocalMap進行set(),get(),remove()的時候,都會進行清理:

以getEntry()為例:

private Entry getEntry(ThreadLocal<?> key) {     int i = key.threadLocalHashCode & (table.length - 1);     Entry e = table[i];     if (e != null && e.get() == key)         //如果找到key,直接返回         return e;     else         //如果找不到,就會嘗試清理,如果你總是訪問存在的key,那么這個清理永遠不會進來         return getEntryAfterMiss(key, i, e); }

下面是getEntryAfterMiss()的實現:

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {     Entry[] tab = table;     int len = tab.length;      while (e != null) {         // 整個e是entry ,也就是一個弱引用         ThreadLocal<?> k = e.get();         //如果找到了,就返回         if (k == key)             return e;         if (k == null)             //如果key為null,說明弱引用已經被回收了             //那么就要在這里回收里面的value了             expungeStaleEntry(i);         else             //如果key不是要找的那個,那說明有hash沖突,這里是處理沖突,找下一個entry             i = nextIndex(i, len);         e = tab[i];     }     return null; }

真正用來回收value的是expungeStaleEntry()方法,在remove()和set()方法中,都會直接或者間接調用到這個方法進行value的清理:

從這里可以看到,ThreadLocal為了避免內存泄露,也算是花了一番大心思。不僅使用了弱引用維護key,還會在每個操作上檢查key是否被回收,進而再回收value。

但是從中也可以看到,ThreadLocal并不能100%保證不發生內存泄漏。

比如,很不幸的,你的get()方法總是訪問固定幾個一直存在的ThreadLocal,那么清理動作就不會執行,如果你沒有機會調用set()和remove(),那么這個內存泄漏依然會發生。

因此,一個良好的習慣依然是:當你不需要這個ThreadLocal變量時,主動調用remove(),這樣對整個系統是有好處的。

ThreadLocalMap中的Hash沖突處理

ThreadLocalMap作為一個HashMap和java.util.HashMap的實現是不同的。對于java.util.HashMap使用的是鏈表法來處理沖突:

ThreadLocal如何使用

但是,對于ThreadLocalMap,它使用的是簡單的線性探測法,如果發生了元素沖突,那么就使用下一個槽位存放:

ThreadLocal如何使用

具體來說,整個set()的過程如下:

ThreadLocal如何使用

可以被繼承的ThreadLocal&mdash;&mdash;InheritableThreadLocal

在實際開發過程中,我們可能會遇到這么一種場景。主線程開了一個子線程,但是我們希望在子線程中可以訪問主線程中的ThreadLocal對象,也就是說有些數據需要進行父子線程間的傳遞。比如像這樣:

public static void main(String[] args) {     ThreadLocal threadLocal = new ThreadLocal();     IntStream.range(0,10).forEach(i -> {         //每個線程的序列號,希望在子線程中能夠拿到         threadLocal.set(i);         //這里來了一個子線程,我們希望可以訪問上面的threadLocal         new Thread(() -> {             System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());         }).start();         try {             Thread.sleep(1000);         } catch (InterruptedException e) {             e.printStackTrace();         }     }); }

執行上述代碼,你會看到:

Thread-0:null Thread-1:null Thread-2:null Thread-3:null

因為在子線程中,是沒有threadLocal的。如果我們希望子線可以看到父線程的ThreadLocal,那么就可以使用InheritableThreadLocal。顧名思義,這就是一個支持線程間父子繼承的ThreadLocal,將上述代碼中的threadLocal使用InheritableThreadLocal:

InheritableThreadLocal threadLocal = new InheritableThreadLocal();

再執行,就能看到:

Thread-0:0 Thread-1:1 Thread-2:2 Thread-3:3 Thread-4:4

可以看到,每個線程都可以訪問到從父進程傳遞過來的一個數據。雖然InheritableThreadLocal看起來挺方便的,但是依然要注意以下幾點:

變量的傳遞是發生在線程創建的時候,如果不是新建線程,而是用了線程池里的線程,就不靈了

變量的賦值就是從主線程的map復制到子線程,它們的value是同一個對象,如果這個對象本身不是線程安全的,那么就會有線程安全問題

感謝各位的閱讀!關于“ThreadLocal如何使用”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

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

AI

江油市| 昔阳县| 南安市| 崇义县| 沙湾县| 健康| 泸西县| 封开县| 凤阳县| 武邑县| 塘沽区| 蓬莱市| 潜江市| 二手房| 克山县| 会泽县| 都安| 阳城县| 邵阳县| 九龙县| 台湾省| 苗栗市| 富川| 晋宁县| 渭南市| 临沧市| 峨山| 鄯善县| 浪卡子县| 馆陶县| 杂多县| 梁河县| 平邑县| 杭锦旗| 彝良县| 柘城县| 安泽县| 乡城县| 抚远县| 夹江县| 搜索|