ThreadLocal解析與最佳實(shí)踐

概述

用于同一個(gè)線程內(nèi)的方法要共享某些變量或狀態(tài)的時(shí)候,提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或者組件之間一些公共變量的傳遞的復(fù)雜度

源碼解讀

源碼的閱讀主要集中在幾個(gè)關(guān)鍵方法

構(gòu)造函數(shù)

  /**
     * Creates a thread local variable.
     */
    public ThreadLocal() {
    }

可以看出,默認(rèn)的構(gòu)造函數(shù)什么都沒有干,但如果需要設(shè)置初始值怎么辦

initialValue()

    protected T initialValue() {
        return null;
    }

使用者可以通過繼承ThreadLocal覆蓋該方法來設(shè)置初始值,該值在第一次調(diào)用get()方法時(shí)被調(diào)用,該方法在整個(gè)ThreadLocal的生命周期中應(yīng)該只對調(diào)用一次,除非用戶顯示地調(diào)用了remove(),然后又調(diào)用get()時(shí)會再次調(diào)用initialValue

get()

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

get方法中可以看出先得到當(dāng)前線程的threadlocalmap,如果不存在該map(首次調(diào)用get()),則調(diào)用setInitialValue(),如果存在則得到當(dāng)前Key對應(yīng)的值

setInitialValue()

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

先調(diào)用intialValue得到初始值,然后得到該線程對應(yīng)的ThreadLocalMap,然后在Map中set初始值,如果沒有ThreadLocalMap則創(chuàng)建,并設(shè)置當(dāng)前TheadLocal初始值.從上可以看出,初始化的時(shí)候可能做兩件事
1、已有map
則將ThreadLocal作為key,initialValue為value放入到map中

2、沒有map
新建一個(gè)ThreadLocalMap,并將<key,value>放入其中

createMap()

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

set(T value)

   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

和setInitialValue方法類似

remove()

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

比較重要的一個(gè)方法,將當(dāng)前的threadlocal變量從map中移除。

tips

比較重要的一點(diǎn)是,ThreadLocal,Thread,ThreadLocalMap的設(shè)計(jì)
目前的設(shè)計(jì)是Thread中有ThreadLocalMap,Map中以ThreadLocal為key,這種設(shè)計(jì)非常的清晰,由于在ThreadLocalMap中ThreadLocal是以WeakReference的形式存在的,所以其引用鏈或如下所示,也會產(chǎn)生GC疑問:ThreadLocal被回收,但是map中的entry一直不能回收的問題。
所以引出了最佳實(shí)踐問題

threadlocal的引用鏈

ThreadLocal引用鏈.png

最佳實(shí)踐

最佳實(shí)踐的方法參見google guava eventbus中對于ThreadLocal的使用


    private final ThreadLocal<Boolean> dispatching;
    
    this.dispatching = new ThreadLocal() {
                protected Boolean initialValue() {
                    return Boolean.valueOf(false);
                }
            }
    if(!((Boolean)this.dispatching.get()).booleanValue()) {
                this.dispatching.set(Boolean.valueOf(true));

                Dispatcher.PerThreadQueuedDispatcher.Event nextEvent;
                try {
                    while((nextEvent = (Dispatcher.PerThreadQueuedDispatcher.Event)queueForThread.poll()) != null) {
                        while(nextEvent.subscribers.hasNext()) {
                            ((Subscriber)nextEvent.subscribers.next()).dispatchEvent(nextEvent.event);
                        }
                    }
                } finally {
                    this.dispatching.remove();
                    this.queue.remove();
                }
            }

  • 采用匿名內(nèi)部類賦初始值
  • 顯式調(diào)用get()、set()
  • 在不用的時(shí)候顯式地remove()掉
  • 對于顯示的remove特別重要,因?yàn)檫@樣可以避免entry不被GC的情況
    如果為了避免ThreadLocal被GC,可以加強(qiáng)ThreadLocal的引用,將其聲明成private static

致謝

本文參考了解密ThreadLocal,引用了文中的圖片,感謝作者,侵刪。

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

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

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