深入理解Threadlocal

前言

并發(fā)是Java開(kāi)發(fā)中繞不開(kāi)的一個(gè)話(huà)題?,F(xiàn)代處理器都是多核心,想要更好地榨干機(jī)器的性能,多線(xiàn)程編程是必不可少,所以,線(xiàn)程安全是每位Java Engineer的必修課。

應(yīng)對(duì)線(xiàn)程安全問(wèn)題,可大致分為兩種方式:

  1. 同步: 用Synchronized關(guān)鍵字,或者用java.util.concurrent.locks.Lock工具類(lèi)給臨界資源加鎖。
  2. 避免資源爭(zhēng)用: 將全局資源放在ThreadLocal變量中,避免并發(fā)訪(fǎng)問(wèn)。

本文將介紹第二種方式:ThreadLocal的實(shí)現(xiàn)原理以及為什么能保證線(xiàn)程安全。

ThreadLocal

下面是ThreadLocal的一個(gè)常見(jiàn)使用場(chǎng)景:

public class ThreadLocalTest {
    // 一般都將ThreadLocal定義為靜態(tài)變量
    private static final ThreadLocal<DateFormat> format = new ThreadLocal<DateFormat>(){
        // 初始化ThreadLocal的值
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static void main(String[] args) {
        // 啟動(dòng)20個(gè)線(xiàn)程
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                try {
                    // 得到SimpleDateFormat在本線(xiàn)程中的副本
                    DateFormat localFormat = format.get();
                    // 解析日期,這里并不會(huì)報(bào)錯(cuò)
                    Date date = localFormat.parse("2000-11-11 11:11:11");
                    System.out.println(date);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

大家應(yīng)該都知道,Java中SimpleDateFormat不是線(xiàn)程安全的,參考這篇文章。然而上述代碼的確不會(huì)報(bào)錯(cuò),說(shuō)明ThreadLocal確實(shí)能保證并發(fā)安全。

源碼解析

ThreadLocal概覽

上面的例子中,我們調(diào)用了ThreadLocalinitialValueget方法,且來(lái)看一下get方法的實(shí)現(xiàn):

// 此類(lèi)的作者是兩個(gè)大神,前者是《Effective Java》的作者,后者是Java并發(fā)包的作者,并發(fā)大師
/*
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
    public T get() {
        // 得到當(dāng)前線(xiàn)程
        Thread t = Thread.currentThread();
        // 根據(jù)當(dāng)前線(xiàn)程,拿到一個(gè)Map,暫且可以將之類(lèi)比為HashMap鍵值對(duì)形式
        // 可見(jiàn)這個(gè)Map是與本線(xiàn)程相關(guān)的
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 通過(guò)this從Map中拿Entry,說(shuō)明Map中的Key就是ThreadLocal變量本身
            // value就是ThreadLocal中所保存的對(duì)象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 若Map沒(méi)有初始化(map == null),或者當(dāng)前ThreadLocal變量沒(méi)有初始化(e == null)
        // 則調(diào)用此方法完成初始化
        return setInitialValue();
    }

    // 原來(lái),這個(gè)ThreadLocalMap只是線(xiàn)程的一個(gè)成員變量!
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

public class Thread implements Runnable {
    // Thread類(lèi)中定義了一個(gè)全局變量ThreadLocalMap
    // 用來(lái)存放本線(xiàn)程中所有的ThreadLocal類(lèi)型變量,初始值為null
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

get方法,我們可以得到如下信息:

  1. ThreadLocal變量保存在一個(gè)Map中,而這個(gè)Map正是Thread類(lèi)的一個(gè)全局變量。這也是ThreadLocal實(shí)現(xiàn)線(xiàn)程安全的一個(gè)關(guān)鍵點(diǎn):各個(gè)線(xiàn)程都有自己的Map,每個(gè)線(xiàn)程操作的都是自己的ThreadLocal變量副本,互不影響。
  2. ThreadLocalMap保存線(xiàn)程中所有的ThreadLocal變量,ThreadLocal變量是Key,ThreadLocal所對(duì)應(yīng)的值為Value。(在本文開(kāi)始的例子中,Key為format變量,Value為initialValue方法返回的值new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  3. ThreadLocal是懶加載的,當(dāng)發(fā)現(xiàn)ThreadLocalMap或者當(dāng)前ThreadLocal變量未初始化時(shí),會(huì)調(diào)用setInitialValue方法進(jìn)行初始化。
ThreadLocal

ThreadLocal其他方法

繼續(xù)來(lái)看setInitialValue方法做了什么事情:

    private T setInitialValue() {
        // 調(diào)用initialValue方法初始化
        // 這個(gè)方法即為我們定義ThreadLocal變量的時(shí)候重寫(xiě)的方法
        T value = initialValue();
        Thread t = Thread.currentThread();
        // 獲取當(dāng)前線(xiàn)程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 如果Map已經(jīng)初始化好了,那直接初始化當(dāng)前ThreadLocal變量:
            // 將自己(當(dāng)前ThreadLocal變量)作為key,保存的值作為value,set到Map里面去
            map.set(this, value);
        else
            // 如果Map還未初始化,則初始化Map
            createMap(t, value);
        return value;
    }

    // 默認(rèn)的initialValue方法定義為protected,就是給我們重寫(xiě)的
    protected T initialValue() {
        return null;
    }

    void createMap(Thread t, T firstValue) {
        // 新建一個(gè)ThreadLocalMap,賦值給當(dāng)前Thread
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

其他還有setremove方法,很簡(jiǎn)單這里不另外講解。

難道ThreadLocal就此結(jié)束了么?有這么簡(jiǎn)單么?當(dāng)然沒(méi)有。因?yàn)?strong>ThreadLocalMap是Thread的一個(gè)成員變量,所以它的生命周期跟線(xiàn)程是一樣長(zhǎng)的。也就是說(shuō),只要線(xiàn)程還沒(méi)有被銷(xiāo)毀,那么Map就會(huì)常駐內(nèi)存,無(wú)法被GC,很容易造成內(nèi)存泄漏。那ThreadLocal是如何解決的呢?

答案是弱引用,Java中的引用類(lèi)型,可以參考這篇文章。

ThreadLocalMap

ThreadLocalMapThreadLocal的一個(gè)內(nèi)部類(lèi)。Java中有現(xiàn)成的類(lèi)HashMap,而ThreadLocal又費(fèi)勁千辛萬(wàn)苦自己實(shí)現(xiàn)了一個(gè)ThreadLocalMap,就是為防止內(nèi)存泄漏。

下面我們來(lái)探秘ThreadLocalMap,它跟普通的HashMap有什么區(qū)別。

ThreadLocalMap的數(shù)據(jù)結(jié)構(gòu)

static class ThreadLocalMap {

    // 內(nèi)部類(lèi)Entry繼承了WeakReference
    static class Entry extends WeakReference<ThreadLocal<?>> {
        // ThreadLocal變量中保存的值
        Object value;

        // 可以看到,Entry只是簡(jiǎn)單的Key-Value,并沒(méi)有類(lèi)似HashMap中的鏈表
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    // ThreadLocalMap默認(rèn)大小
    private static final int INITIAL_CAPACITY = 16;
    // 此Entry數(shù)組,就是所有ThreadLocal存放的地方
    private Entry[] table;
}

ThreadLocalMap維護(hù)了一個(gè)Entry數(shù)組(沒(méi)有鏈表,這是跟HashMap不一樣的地方),用來(lái)存放線(xiàn)程中所有的ThreadLocal變量。Entry繼承了WeakReference,并關(guān)聯(lián)了ThreadLocal,當(dāng)外界沒(méi)有其他強(qiáng)引用指向ThreadLocal對(duì)象時(shí),該ThreadLocal對(duì)象會(huì)在下一次GC時(shí)被內(nèi)存回收,也就是Entry中的Key會(huì)被回收掉,所以下面會(huì)看到清理key為null的Entry的操作。

Set操作

當(dāng)HashMap遇到哈希沖突的時(shí)候,是通過(guò)在同一個(gè)Hash Key上建立鏈表來(lái)解決的。既然ThreadLocalMap只維護(hù)了一個(gè)Entry數(shù)組,那它是怎么解決哈希沖突的呢?我們來(lái)看set方法的源碼:

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        // 根據(jù)ThreadLocal的hashcode,計(jì)算出在table中的槽位(index)
        int i = key.threadLocalHashCode & (len-1);
        // 從位置i開(kāi)始,逐個(gè)往后循環(huán),找到第一個(gè)空的槽位(條件e == null)
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            // 如果key相等,則直接將舊value覆蓋掉,換成新value
            if (k == key) {
                // 新值替換掉舊值,并return掉
                e.value = value;
                return;
            }
            // key == null,說(shuō)明弱引用之前已經(jīng)被內(nèi)存回收,則將值設(shè)在此槽位
            if (k == null) {
                // 該方法后面再解析
                replaceStaleEntry(key, value, i);
                return;
            }
        }

        // 走到這里,這個(gè)i 是從key真正所在的hash槽之后數(shù),第一個(gè)非空槽位
        // 將value包裝成Entry,放到位置i中
        tab[i] = new Entry(key, value);
        int sz = ++size;
        // 查找是否有Entry已經(jīng)被回收
        // 如果找到有Entry被回收,或者table的size大于閾值,執(zhí)行rehash操作
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    
    // 獲取下一個(gè)index。其實(shí)就是i + 1。當(dāng)超出table長(zhǎng)度的時(shí)候,歸0重新來(lái)
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

ThreadLocalMap是用開(kāi)放地址發(fā)來(lái)解決哈希沖突的。如果目標(biāo)槽位已經(jīng)有值了,首先判斷該值是不是就是自己。如果是,那就替換舊值;如果不是,再判斷該槽位的值是否有效(槽位上的ThreadLocal變量有沒(méi)有被垃圾回收),如果無(wú)效,則直接設(shè)置在該槽位,并執(zhí)行一些清理操作。如果該槽位上是一個(gè)有效的值,那么往后繼續(xù)尋找,直到找到空槽位為止。流程大概如下:

ThreadLocalMap

清理無(wú)效的Entry

到這里,我們應(yīng)該帶著一個(gè)疑問(wèn):弱引用清除的只是Entry中的key,也就是ThreadLocal變量,而Entry本身依然占據(jù)著table中的槽位。那代碼中是在哪里清理這些無(wú)效的Entry的呢?我們重點(diǎn)看一下上面沒(méi)有分析的兩個(gè)方法replaceStaleEntrycleanSomeSlots

cleanSomeSlots

    // 顧名思義,清除部分槽位,默認(rèn)掃描log(n)個(gè)槽位
    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];
            // 注意無(wú)效Entry的判斷條件是,e.get() == null
            // 即Entry中保存的弱引用已經(jīng)被GC,這種情況需要將對(duì)應(yīng)Entry清除
            if (e != null && e.get() == null) {
                // 如果發(fā)現(xiàn)有無(wú)效entry,那n會(huì)重新設(shè)置為table的長(zhǎng)度
                // 即會(huì)繼續(xù)查找log(n)個(gè)槽位,判斷有沒(méi)有無(wú)效Entry
                n = len;
                removed = true;
                // 調(diào)用expungeStaleEntry方法清除i位置的槽位
                i = expungeStaleEntry(i);
            }
        // 循環(huán)條件為n右移一位,即除以2。所以默認(rèn)是循環(huán)log(n)次
        } while ( (n >>>= 1) != 0);
        // 如果有槽位被清除,返回true
        return removed;
    }

    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
        // 將i位置的槽位置為空
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;

        Entry e;
        int i;
        // 繼續(xù)往后檢查是否有無(wú)效Entry,直到遇到空的槽位tab[i]==null為止
        for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            // 如果Entry無(wú)效,將其清除
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                // 重新計(jì)算hash值h
                int h = k.threadLocalHashCode & (len - 1);
                // 如果新hash值h不等于當(dāng)前位置的槽位值i,這種情況需要rehash
                // 給當(dāng)前i位置的e,重新找更合理的槽位
                if (h != i) {
                    // 將i位置置空
                    tab[i] = null;
                    // 從h位置往后找第一個(gè)空槽位
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    // 將e放在第一個(gè)空槽位上
                    tab[h] = e;
                }
            }
        }
        // 返回接下來(lái)第一個(gè)空槽位的下標(biāo)
        return i;
    }

cleanSomeSlots方法會(huì)掃描部分的槽位,查看是否有無(wú)效的Entry。如果沒(méi)有找到,那么只掃描log(n)個(gè)槽位;如果有找到無(wú)效槽位,則會(huì)清除該槽位,并額外再掃描log(n)個(gè)槽位,以此類(lèi)推。
清空槽位的工作是expungeStaleEntry方法做的,除了清除當(dāng)前位置的Entry之外,它還會(huì)檢查往后連續(xù)的非空Entry,并清除其中無(wú)效值。同時(shí)還會(huì)判斷并處理rehash。這里為什么要rehash?因?yàn)榍懊嬗袩o(wú)效Entry被清除掉了,如果后面的Entry是因?yàn)閔ash沖突而被延到后面的,就可以把后面的Entry移到前面空出來(lái)的位置上,從而提高查詢(xún)效率。

cleanSomeSlots舉例

CleanSomeSlots Example

上圖的情況,我們分兩種情況討論:

  • 如果從i=2開(kāi)始找:

    1. tab[2]所在位置為null,繼續(xù)循環(huán)i=nextIndex(i, len)=nextIndex(2, 8)=3
    2. tab[3]所在位置(k3,v3)有效,繼續(xù)循環(huán)i=nextIndex(i, len)=nextIndex(3, 4)=0
    3. tab[0]所在位置(k1,v1)有效,繼續(xù)循環(huán)i=nextIndex(i, len)=nextIndex(0, 2)=1
    4. tab[1]所在位置(k2,v2)有效,繼續(xù)循環(huán)i=nextIndex(i, len)=nextIndex(1, 1)=0
    5. tab[0]所在位置(k1,v1)有效,n==0結(jié)束
  • 如果從i=11開(kāi)始找:

    1. tab[11]所在位置(null,v7)無(wú)效,調(diào)用expungeStaleEntry方法,expungeStaleEntry方法清空tab[11],并會(huì)往后循環(huán)判斷。因?yàn)閠ab[12]位置(null,v8)無(wú)效,所以tab[12]也會(huì)被清空;tab[13]位置(k9,v9)有效,則會(huì)判斷是否需要給(k9,v9)重新放位置。如果對(duì)k9執(zhí)行rehash之后依然是12,則不作處理;如果對(duì)k9執(zhí)行rehash之后是11,說(shuō)明該元素是因?yàn)閔ash碰撞被放到了12的位置,那么需要把元素放到tab[11]的位置。expungeStaleEntry方法返回第一個(gè)為null的下標(biāo)14,n重新設(shè)置為16,i=nextIndex(i, len)=nextIndex(14, 16)=15
    2. tab[15]所在位置(k10,v10)有效,繼續(xù)循環(huán)i=nextIndex(i, len)=nextIndex(15, 8)=0
    3. tab[0]所在位置(k1,v1)有效,繼續(xù)循環(huán)i=nextIndex(i, len)=nextIndex(0, 2)=1
    4. tab[1]所在位置(k2,v2)有效,繼續(xù)循環(huán)i=nextIndex(i, len)=nextIndex(1, 1)=0
    5. tab[0]所在位置(k1,v1)有效,n==0結(jié)束

replaceStaleEntry方法

    private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
        Entry e;
        // slotToExpunge記錄了包含staleSlot的連續(xù)段上,第一個(gè)無(wú)效Entry的下標(biāo)
        int slotToExpunge = staleSlot;
        // 往前遍歷非空槽位,找到第一個(gè)無(wú)效Entry的下標(biāo),記錄為slotToExpunge
        for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len))
            if (e.get() == null)
                slotToExpunge = i;
        // 往后遍歷非空段,查找key所在的位置,即檢查key之前是否之前已經(jīng)被添加過(guò)
        // 為什么到tab[i]==null為止?因?yàn)榭盏牟壑蟮膆ash值肯定已經(jīng)不一樣
        for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            if (k == key) {
                // 如果找到了key,那么說(shuō)明此key之前已經(jīng)添加過(guò),直接覆蓋舊值
                // 因?yàn)閟taleSlot小于i,需要將兩個(gè)槽位的值進(jìn)行交換,以提高查詢(xún)效率
                // 而被換到i處的無(wú)效Entry,會(huì)在之后的cleanSomeSlots被清除掉
                e.value = value;
                tab[i] = tab[staleSlot];
                tab[staleSlot] = e;
                // 如果slotToExpunge的值并沒(méi)有變,說(shuō)明往前查找的過(guò)程中并未發(fā)現(xiàn)無(wú)效Entry
                // 那么以當(dāng)前位置作為cleanSomeSlots的起點(diǎn)
                if (slotToExpunge == staleSlot)
                    slotToExpunge = i;
                // 這兩個(gè)方法都已經(jīng)分析過(guò),從slotToExpunge位置開(kāi)始清理無(wú)效Entry
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                return;
            }

            // 如果前面往前查找沒(méi)有發(fā)現(xiàn)無(wú)效Entry,且此處的Entry無(wú)效(k==null)
            // 那么將說(shuō)明i處是第一個(gè)無(wú)效Entry,將slotToExpunge計(jì)為i
            if (k == null && slotToExpunge == staleSlot)
                slotToExpunge = i;
        }
        // 如果key沒(méi)有找到,說(shuō)明這是一個(gè)新Entry,那么直接新建一個(gè)Entry放在staleSlot位置
        tab[staleSlot].value = null;
        tab[staleSlot] = new Entry(key, value);
        if (slotToExpunge != staleSlot)
            // 這兩個(gè)方法都已經(jīng)分析過(guò),從slotToExpunge位置開(kāi)始清理無(wú)效Entry
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }

這個(gè)方法其實(shí)就是三個(gè)步驟:

  1. 往后查找該key在table中是否存在。如果存在,即之前已經(jīng)set過(guò)該key,那么需要覆蓋掉舊值,并且將key所在元素移到staleSlot位置。(為什么要移位置?因?yàn)樵厮诘奈恢胕,肯定在staleSlot之后,所以將元素往前放到staleSlot上可以提高查詢(xún)效率,并避免后續(xù)的rehash操作。)
  2. 如果key不存在,說(shuō)明是新set的操作,直接新建Entry,放在staleSlot位置。
  3. 調(diào)用cleanSomeSlots方法,清除無(wú)效的Entry

其他方法

剩下的方法都比較簡(jiǎn)單,解析見(jiàn)源碼注釋?zhuān)涣硗饨忉?br> get方法:

    // get操作的方法
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        // i位置元素即為要找的元素,直接返回
        if (e != null && e.get() == key)
            return e;
        else
            // 否則調(diào)用getEntryAfterMiss方法
            return getEntryAfterMiss(key, i, e);
    }

    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
        // 從i位置開(kāi)始,往后遍歷查找,直到空槽位為止。為什么到空槽位為止?
        // 根據(jù)開(kāi)地址法,空槽位之后的元素hash值肯定已經(jīng)不一樣,沒(méi)必要再繼續(xù)
        while (e != null) {
            ThreadLocal<?> k = e.get();
            // key相等,這就是目標(biāo)元素,直接返回
            if (k == key)
                return e;
            // key為null,則是無(wú)效元素,調(diào)用expungeStaleEntry方法清除i位置的元素
            if (k == null)
                expungeStaleEntry(i);
            else
                // 繼續(xù)尋找下一個(gè)元素
                i = nextIndex(i, len);
            e = tab[i];
        }
        // 沒(méi)有找到目標(biāo)元素,返回null
        return null;
    }

remove方法:

    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)]) {
            // 找到目標(biāo)元素
            if (e.get() == key) {
                e.clear();
                // 調(diào)用expungeStaleEntry方法清除i位置的元素
                expungeStaleEntry(i);
                return;
            }
        }
    }

resize方法

    // 當(dāng)元素個(gè)數(shù)大于threshold(默認(rèn)是table長(zhǎng)度的2/3)時(shí),需要resize
    private void resize() {
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        // 新table長(zhǎng)度是舊table的2倍
        int newLen = oldLen * 2;
        Entry[] newTab = new Entry[newLen];
        int count = 0;
        // 遍歷舊table
        for (int j = 0; j < oldLen; ++j) {
            Entry e = oldTab[j];
            if (e != null) {
                ThreadLocal<?> k = e.get();
                // 如果key為null,則這是個(gè)無(wú)效Entry,直接跳過(guò)(將值置為空方便GC)
                if (k == null) {
                    e.value = null; // Help the GC
                } else {
                    // 根據(jù)新table的長(zhǎng)度重新計(jì)算hash值
                    int h = k.threadLocalHashCode & (newLen - 1);
                    // 根據(jù)開(kāi)地址法,從h開(kāi)始找到第一個(gè)空槽位
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    // 將該值放到該位置
                    newTab[h] = e;
                    count++;
                }
            }
        }
        // 設(shè)置新table的一些參數(shù)
        setThreshold(newLen);
        size = count;
        table = newTab;
    }

總結(jié)

本文從代碼層面,深入介紹了ThreadLocal的實(shí)現(xiàn)原理。
ThreadLocal可以保證線(xiàn)程安全,是因?yàn)樗o為每個(gè)線(xiàn)程都創(chuàng)建了一個(gè)變量的副本。每個(gè)線(xiàn)程訪(fǎng)問(wèn)的都是自己內(nèi)部的變量,不會(huì)有并發(fā)沖突。
作為線(xiàn)程內(nèi)部變量,它跟局部變量有什么區(qū)別呢?一般ThreadLocal都被定義為static,也就是說(shuō),每個(gè)線(xiàn)程只需要?jiǎng)?chuàng)建一份,生命周期跟線(xiàn)程一樣。而局部變量生命周期跟方法與方法一樣,每調(diào)用一次方法,創(chuàng)建一次變量,方法結(jié)束,對(duì)象銷(xiāo)毀。ThreadLocal可以避免一些大對(duì)象的重復(fù)創(chuàng)建銷(xiāo)毀。

ThreadLocalMapEntry繼承自WeakReference,當(dāng)沒(méi)有其他的強(qiáng)引用指向ThreadLocal變量時(shí),該ThreadLocal變量會(huì)在下次GC中被回收。對(duì)于被回收掉的ThreadLocal變量,不會(huì)顯式地去清理,而是在接下來(lái)的get,set,remove操作中去檢查刪除掉這些無(wú)效ThreadLocal變量所在的Entry,防止可能的內(nèi)存泄漏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容