亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

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

如何進行ThreadLocal源碼分析

發布時間:2021-12-17 15:27:25 來源:億速云 閱讀:164 作者:柒染 欄目:大數據

如何進行ThreadLocal源碼分析,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

1.ThreadLocal簡介

ThreadLocal是線程本地變量,ThreadLocal為每一個線程創建一個單獨的變量副本ThreadLocalMap,所以每個線程修改自己變量副本不會影響其它的線程。區別于線程同步,我們知道線程同步是為了解決多線程下共享變量的安全問題,而ThreadLocal是為了解決線程內部數據傳遞問題。一個線程內部可以有多個ThreadLocal,但是它門維護線程的同一個ThreadLocalMap變量,共用同一個Entry數組。

ThreadLocal數據結構:

如何進行ThreadLocal源碼分析

每個線程內部有一個ThreadLocalMap屬性,ThreadLocal通過維護該屬性來保證單個線程內部數據共享。ThreadLocalMap內部有一個entry數組,該數組是key,value型結構,key為當前ThreadLocal的弱引用,value用于存放具體的值,類型為一個泛型結構,支持各種數據變量。ThredLocalMap內Entry數組的下標值也是通過 key.threadLocalHashCode & (數組長度 - 1)來確定的,只不過這個threadLocalHashCode 是通過AutomicLong每次遞增0x61c88647來確定的,這可以盡量減少hash碰撞。不同于HashMap,ThreadLocalMap內部只維護了一個Entry數組,所以當發生hash沖突的時候,ThreadLocalMap會將發生hash沖突的Entry放在當前key對應數組下標后面第一個為空的數組槽位內。ThreadLocal的擴容閾值默認為數組大小的 2/3。因為Entry的key為當前threadlcoal的弱引用,所以在發生gc的時候容易導致key被回收,但是此時value為強引用,所以這種情況會導致內存溢出。但是,當我們調用threadlocal的set,get,remove方法的時候,ThreadLocalMap內都會發生回收過期key的操作,不過這種回收是一種抽樣回收,可能并不能回收所有的過期key。而且在執行set方法回收的時候,可能發生擴容,這時候的擴容判斷是當前數組的長度的1/2。Entry數組默認初始化長度為16。

2.ThreadLocal簡單示例

public class ThreadLocalTest {    private static final ThreadLocal<String> threadLocal = new ThreadLocal();    private static String str = null;    public static void print1() {        System.out.println("打印方法1輸出:" + threadLocal.get());    }    public static void print2() {        System.out.println("打印方法2輸出:" + str);    }    public static void main(String[] args) {        //線程1        new Thread(() -> {            threadLocal.set("線程1設置的str1");            str = "線程1設置的str2";            //睡5秒鐘            try {                Thread.sleep(5000);            } catch (InterruptedException e) {                e.printStackTrace();            }            //睡5秒鐘后打印,此時第2個線程早已執行完            print1();            print2();        }).start();        //線程2        new Thread(() -> {            threadLocal.set("線程2設置的str1");            str = "線程2設置的str2";            //直接打印            print1();            print2();        }).start();    }}

運行結果:

打印方法1輸出:線程2設置的str1打印方法2輸出:線程2設置的str2打印方法1輸出:線程1設置的str1打印方法2輸出:線程2設置的str2

根據運行結果分析出,使用ThreadLocal的存儲的變量在多線程不存在線程安全問題,常規創建的屬性在多線程下存在線程安全問題。

3.ThreadLocal源碼分析

3.1.ThreadLocal的屬性分析

ThreadLocal中使用了斐波那契散列法,來保證哈希表的離散度。可以保證 nextHashCode 生成的哈希值,均勻的分布在 2 的冪次方上。具體的數學問題不在這里深究。

private final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode =    new AtomicInteger();//十進制1640531527=0.618*2^32,這個值是黃金分割率*2^32private static final int HASH_INCREMENT = 0x61c88647;//每次調用該方法,hashcode值就會遞增HASH_INCREMENTprivate static int nextHashCode() {    return nextHashCode.getAndAdd(HASH_INCREMENT);}
//用于計算數組下標的值,table.length - 1轉二進制有N個1,那么//key.threadLocalHashCode & (table.length - 1)的值就是threadLocalHashCode的低N位int i = key.threadLocalHashCode & (table.length - 1);

4.ThreadLocal.set方法分析

public void set(T value) {    //獲取當前線程    Thread t = Thread.currentThread();    //根據當前線程獲取ThreadLocalMap    ThreadLocalMap map = getMap(t);    //如果map為空則創建一個,否則設置屬性值    if (map != null)        //key為當前thread的引用則設置該值        map.set(this, value);    else        //map為空則創建當前線程的ThreadMap并和當前線程綁定        createMap(t, value);}

4.1.ThreadLocalMap.set方法分析

private void set(ThreadLocal<?> key, Object value) {    //將初始化后的當前數組賦值給臨時數組tab    Entry[] tab = table;    //獲取當前臨時tab數組長度    int len = tab.length;    //計算當前key對應的數組下標    int i = key.threadLocalHashCode & (len-1);    //從當前下標開始循環往后遍歷,如果當前數組槽為空,則直接跳出循環,如果不為空,則進行key的判斷    //因為ThreadLocalMap的結構只是數組,沒有鏈表,當key發生沖突,    //不同的key定位到相同的數組下標的時候,會往后尋找第一個下標為null    //的槽或者第一個key位過期key的槽,并將entry放入并賦值    for (Entry e = tab[i];         e != null;         e = tab[i = nextIndex(i, len)]) {        //對應下標為i的槽位為空的時候才會走到循環里面的邏輯        //獲取key        ThreadLocal<?> k = e.get();        //CASE1:如果key相同,替換value并跳出循環        if (k == key) {            e.value = value;            return;        }        //CASE2:如果key為空,說明key已經過期了,當前下標對應的槽可以被使用        if (k == null) {            //替換過期key的邏輯            replaceStaleEntry(key, value, i);            return;        }    }    //如果當前下標下的數組槽為空,占用該槽位并賦值    tab[i] = new Entry(key, value);    //遞增數組大小    int sz = ++size;    //沒有清理到數據,且size大小達到了擴容閾值    if (!cleanSomeSlots(i, sz) && sz >= threshold)        rehash();}

4.2.ThreadLocalMap.replaceStaleEntry方法分析

給當前key找數組槽位的時候,找到的下標對應的key為過期的key的時候,執行替換操作

private void replaceStaleEntry(ThreadLocal<?> key, Object value,                               int staleSlot) {    //數組列表    Entry[] tab = table;    //數組長度    int len = tab.length;    //臨時變量    Entry e;    //需要清理的數據的開始下標,默認為當前staleSlot    int slotToExpunge = staleSlot;    //從當前staleSlot向前查找,找對應數組槽下的entry,直到碰到空的槽則退出循環    for (int i = prevIndex(staleSlot, len);         (e = tab[i]) != null;         i = prevIndex(i, len))        //如果在查找過程中,碰到key為過期key的情況,更新需要清理的數據的開始下標        if (e.get() == null)            slotToExpunge = i;    //從當前staleSlot向后查找,找對應數組槽下的entry,直到碰到空的槽則退出循環    for (int i = nextIndex(staleSlot, len);         (e = tab[i]) != null;         i = nextIndex(i, len)) {        //獲取當前元素的key        ThreadLocal<?> k = e.get();        //如果key相同,則替換value,遷移數據位置        if (k == key {            e.value = value;            //將過期的tab[staleSlot]放到找到的i下標下            tab[i] = tab[staleSlot];            //當前staleSlot下標下的槽替換為當前的entry,數據的位置被優化了            tab[staleSlot] = e;            //條件成立說明向前過程中并沒有找到過期的key            if (slotToExpunge == staleSlot)                //修改需要清理數據的開始下標為替換數據后的下標                slotToExpunge = i;            //清理數據            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);            return;        }                //k==null說明循環過程中未找到匹配的key        //slotToExpunge == staleSlot說明向前遍歷過程中未找到過期的key        if (k == null && slotToExpunge == staleSlot)            //可以將循環向后查找的i指向slotToExpunge,因為在向后查找的過程中沒有找到相同的key            //該段期間沒必要處理了            slotToExpunge = i;    }    //走到這里說明循環向后查找的過程中,沒有找到相同的key    //直接使用當前下標并賦值            tab[staleSlot].value = null;    tab[staleSlot] = new Entry(key, value);     //條件成立,說明在向前向后遍歷中,slotToExpunge被改變了      if (slotToExpunge != staleSlot)        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}

4.3.ThreadLocalMap.cleanSomeSlots方法分析

為什么有while ( (n >>>= 1) != 0),這樣不是可能清理不了所有數據嗎?是的,ThreadLocal的設計行就是部分清除,類似于抽樣,避免清理所有影響性能。

private boolean cleanSomeSlots(int i, int n) {    boolean removed = false;    Entry[] tab = table;    int len = tab.length;    do {        i = nextIndex(i, len);        Entry e = tab[i];        if (e != null && e.get() == null) {            n = len;            removed = true;            //執行清理,可能會遷移數據            i = expungeStaleEntry(i);        }    } while ( (n >>>= 1) != 0);    return removed;}

4.4.ThreadLocalMap.rehash擴容操作

擴容之前,進行一次全面的清理操作

private void rehash() {    expungeStaleEntries();    if (size >= threshold - threshold / 4)        resize();}

擴容邏輯,比較簡單,數組變大兩倍,舊數據遷移到新數組,如果key已經過期的,則直接將value也設置為空。這里需要注意的時候,清理過程中擴容的閾值是原數組容量的 1/2, size >= threshold - threshold / 4,我們直到threashold = 2 / 3 * length, 所以轉化后size >= 3 / 4 * (2 / 3) * length。

private void resize() {    Entry[] oldTab = table;    int oldLen = oldTab.length;    int newLen = oldLen * 2;    Entry[] newTab = new Entry[newLen];    int count = 0;    for (int j = 0; j < oldLen; ++j) {        Entry e = oldTab[j];        if (e != null) {            ThreadLocal<?> k = e.get();            //如果對應的key已經回收            if (k == null) {                //value設置為空                e.value = null; // Help the GC            } else {                //進行數據遷移,如果存在沖突,則放到計算出來的下標的后方第一個不為null的槽                int h = k.threadLocalHashCode & (newLen - 1);                while (newTab[h] != null)                    h = nextIndex(h, newLen);                newTab[h] = e;                count++;            }        }    }    //重新設置擴容閾值    setThreshold(newLen);    size = count;    table = newTab;}

5.ThreadLocal.get方法分析

5.1.方法調用過程

如何進行ThreadLocal源碼分析

當我們調用threadLocal的get方法的時候,首先會調用getMap方法,該方法根據當前線程獲取當前線程的ThreadLocal.ThreadLocalMap threadLocals屬性,如果非空,再獲取對應的ThreadLocal的ThreadLocalMap 里面的entry,根據entry獲取對應的value,這個過程會調用expungestaleEntry方法,清空key為空的hash槽的值,并將key不為空的且通過key的hash值計算出來的下標發生過向后偏移的entry移動到更靠近計算出來的下標值的后面的某個空的槽內。如果getMap返回空,說明我們可能沒用調用ThreadLocal的set方法的情況下調用了get方法,那么創建一個ThreadLocalMap,初始化entry數組,設置擴容閾值,并設置對應的ThreadLocal的hash槽的值為空。

public T get() {    //獲取當前線程    Thread t = Thread.currentThread();    //取出當前線程的ThreadLocalMap屬性    ThreadLocalMap map = getMap(t);    //如果當前線程的ThreadLocalMap不為空    if (map != null) {        //獲取ThreadLocalMap的Entry數組        ThreadLocalMap.Entry e = map.getEntry(this);        //如果數組不為空,取出value值返回        if (e != null) {            @SuppressWarnings("unchecked")            T result = (T)e.value;            return result;        }    }    return setInitialValue();}

5.2.ThreadLocal.getMap方法分析

//獲取thread的threadLocals屬性ThreadLocalMap getMap(Thread t) {    return t.threadLocals;}

5.3.ThreadLocalMap.getEntry方法分析

//獲取ThreadLocalMap的entry數組對應下標的數據private Entry getEntry(ThreadLocal<?> key) {    //計算下標    int i = key.threadLocalHashCode & (table.length - 1);    //獲取對應下標數據    Entry e = table[i];    if (e != null && e.get() == key)        return e;    //如果取不到,為什么有這種情況?    //從put方法中我們知道,threadlocalMap不同于hashMap    //內部只有數組,數組的每個hash槽下只有一個entry值    //如果在put的時候發現對應hash槽的值不為空,且key不相同    //則往后找第一個為空的hash槽,講entry放入該hash槽    else        return getEntryAfterMiss(key, i, e);}

5.4.ThreadLocalMap.getEntryAfterMiss方法分析

//從對應下標往后循環查找,這里有個特殊的地方nextIndex//該方法:從對應下標往后循環返回下標,如果超出數組長度,//則從0下標開始繼續往后循環返回下標private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {    Entry[] tab = table;    int len = tab.length;    //循環遍歷    while (e != null) {        ThreadLocal<?> k = e.get();        //case1:key值相同,返回對應的entry        if (k == key)            return e;        //case2:發現對應entry數組下標下的key為空,清理        if (k == null)            expungeStaleEntry(i);        //case3:key不為空但key不相同,數組下標往后推進          else            i = nextIndex(i, len);        //返回下一個下標值對應的entry        e = tab[i];    }    return null;}
//從對應下標往后循環,如果超出數組長度,則從0下標開始繼續往后循環//返回具體下標值private static int nextIndex(int i, int len) {    return ((i + 1 < len) ? i + 1 : 0);}

5.5.ThreadLocalMap.expungeStaleEntry方法分析

從當前staleSlot開始循環清理過期key對應的entry數組內的值;如果key不為空且當前線程對應的threadlocal的hash值計算出來的下標發生過遷移,說明之前在put的時候,在對應下標下發生過hash沖突,將當前下標下的entry數組對應的值置為null,并將當前下標下的entry值移動到更接近通過hash值計算出來的下標之后的某個空的槽中。循環在進行下標右移的過程中,如果碰到對應下標下的槽數據為空,則退出循環。該方法在執行的時候會將本該在staleSlot位置的key對應的變量移動到該位置或更靠近該位置的后方。避免remove方法遍歷的時候出現null導致清理不到的情況。

private int expungeStaleEntry(int staleSlot) {    //將全局entry數組賦值給臨時tab    Entry[] tab = table;    //臨時entry數組當前長度    int len = tab.length;    //設置對應數組下標下的entry的value為空    tab[staleSlot].value = null;    //設置對應entry為空    tab[staleSlot] = null;    //entry數組全局長度-1    size--;    Entry e;    int i;    //從當前下標往后循環遍歷,直到對應的下標下槽內數據為空跳出循環    for (i = nextIndex(staleSlot, len);         (e = tab[i]) != null;         i = nextIndex(i, len)) {        //獲取對應下標下當前entry對應的key        ThreadLocal<?> k = e.get();        //如果key為空則清理entry的value和設置當前數組對應entry為空        if (k == null) {            e.value = null;            tab[i] = null;            size--;        //如果key不為空        } else {            //計算獲取對應的下標,這個本該是存放entry的位置,但是可能由于hash沖突,put的時候向后偏移了            int h = k.threadLocalHashCode & (len - 1);            //條件成立說明在put的時候計算出來的下標發生過hash沖突            //數據向后偏移過,而且 h < i            if (h != i) {                //將當前下標下entry設置為空                tab[i] = null;                //從計算出來的下標h循環向后獲取一個對應entry為空的下標值                //該下標下存放當前entry                while (tab[h] != null)                    //這個新計算出來的h的值更靠近計算獲取的下標                    h = nextIndex(h, len);                //將entry放在對應下標                tab[h] = e;            }        }    //返回進行處理過后的起點下標i    return i;}

5.6.ThreadLocal.setInitialValue方法分析

private T setInitialValue() {    //獲取一個空值    T value = initialValue();    Thread t = Thread.currentThread();    //獲取當前線程的ThreadMap    ThreadLocalMap map = getMap(t);    //如果不為空,則將當前空值注入    if (map != null)        map.set(this, value);    else        //否則創建這個ThreadMap并和當前Thread綁定        createMap(t, value);    return value;}

6.ThreadLocal.remove方法分析

remove方法也很簡單,就是將key的引用設置為null,然后找到key所對應的數組槽位,執行清理操作。

在ThreadLocal使用完畢后,執行remove方法防止內存溢出。

public void remove() {    ThreadLocalMap m = getMap(Thread.currentThread());    if (m != null)        m.remove(this);}
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;        }    }}
public void clear() {    this.referent = null;}

7.InheritableThreadLocal分析

上面說完了ThreadLocal的問題,可以看出,ThreadLocal只能在單個線程內部傳遞參數,無法在子父線程間傳遞參數。

但是InheritableThreadLocal的出現解決了這個問題。

public class InheriTableThreadLocalTest {    private static final InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();    public static void main(String[] args) {        threadLocal.set("主線程設置值");        new Thread(() -> {            System.out.println(threadLocal.get());        }).start();    }}

分析InheritableThreadLocal類,發現繼承于ThreadLocal,但是在createMap,getMap的時候維護的是inheritableThreadLocals

public class InheritableThreadLocal<T> extends ThreadLocal<T> {    protected T childValue(T parentValue) {        return parentValue;    }    ThreadLocalMap getMap(Thread t) {       return t.inheritableThreadLocals;    }    void createMap(Thread t, T firstValue) {        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);    }}

在線程初始化的代碼init方法中,有這么一段邏輯:

如果父線程的inheritThreadLocals不為空,則調用ThreadLocal.createInheritedMap方法,該方法傳遞了父線程的inheritableThreadLocals

if (inheritThreadLocals && parent.inheritableThreadLocals != null)    this.inheritableThreadLocals =        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

再看看ThreadLocal.createInheritedMap方法,子線程在創建的時候,將父線程的inheritableThreadLocals復制了過來保存在了自己的inheritableThreadLocals中。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {    return new ThreadLocalMap(parentMap);}
private ThreadLocalMap(ThreadLocalMap parentMap) {    Entry[] parentTable = parentMap.table;    int len = parentTable.length;    setThreshold(len);    table = new Entry[len];    for (int j = 0; j < len; j++) {        Entry e = parentTable[j];        if (e != null) {            @SuppressWarnings("unchecked")            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();            if (key != null) {                Object value = key.childValue(e.value);                Entry c = new Entry(key, value);                int h = key.threadLocalHashCode & (len - 1);                while (table[h] != null)                    h = nextIndex(h, len);                table[h] = c;                size++;            }        }    }}

看完上述內容,你們掌握如何進行ThreadLocal源碼分析的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

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

AI

广河县| 临湘市| 清徐县| 涿鹿县| 叙永县| 甘谷县| 霍林郭勒市| 玛多县| 犍为县| 平果县| 奉节县| 亚东县| 凤翔县| 玛多县| 安宁市| 招远市| 浦江县| 乐东| 临澧县| 嵊州市| 土默特右旗| 鹿泉市| 六盘水市| 房产| 日照市| 巴塘县| 青川县| 观塘区| 澜沧| 贵德县| 武威市| 敦煌市| 德钦县| 德格县| 宜春市| 章丘市| 泰来县| 深水埗区| 武隆县| 信宜市| 霍州市|