ThreadLocal的原理、作用、使用弱引用原因、應用舉例


一. 原理

  ThreadLocal就是一個類,他有get、set方法,可以起到一個保存、獲取某個值的作用。但是這個類的get、set方法有點特殊,各個線程調用時是互不干擾的,就好像線程在操作ThreadLocal對象時是在操作線程自己的私有屬性一樣。具體原因在於他的方法實現:

public T get() {
        Thread t = Thread.currentThread();  //先確定調用我的線程
        ThreadLocalMap map = getMap(t);  //根據調用我的線程,找到這個線程的ThreadLocalMap對象
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);  //以ThreadLocal對象為key,找到對應元素
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;   //講元素的value返回
                return result;
            }
        }
        return setInitialValue();  //如果調用我的線程沒有ThreadLocalMap對象,則返回初始值
    }
public void set(T value) {
        Thread t = Thread.currentThread();  //先確定調用我的是哪個線程
        ThreadLocalMap map = getMap(t);  //獲取調用我的線程的ThreadLocalMap 
        if (map != null)
            map.set(this, value);  //如果那個線程有map,就將此ThreadLocal對象為key的value設置好
        else
            createMap(t, value);   //如果那個線程還沒有map,先創建一個再設置
    }

ThreadLocalMap是ThreadLocal的內部類,為了不造成混亂,可以把他看作一個普通的類。ThreadLocalMap其實類似與HashMap,也是通過key獲取某個值(key就是ThreadLocal對象),也是數組存儲鍵值對,拉鏈法解決沖突等。一個Thread類持有一個ThreadLocalMap實例。

通過上面的源碼也可以看出:線程互不干擾的操作ThreadLocal的原因就是,它的set、get方法是要先獲取當前線程,然后修改、操作這個線程對象的成員屬性。也就是說,調用ThreadLocal對象的set、get方法實際上是在操作當前線程的成員屬性,只不過這些屬性是通過ThreadLocal對象為key找到的而已。為了值觀明了,看下圖:

 

簡單概括過程:有ThreadLocal對象 tl,線程 t 調用 tl.get(), 則去線程 t 的ThreadLocalMap屬性對象里找到一個entry,若entry.key == tl返回true,則此entry是目標entry,此entry.value就是我們的目標。

ThreadLocal對象只是一個獲取當前線程某個私有屬性的渠道而已,提供了set、get的入口,同時作為key去獲取和設置目標值。真正的有效目標是屬於線程對象私自持有的,自然通過ThreadLocal對象獲取的值也就不會受其他線程影響啦。

 

二. 用法示例

public class ThreadLocalTest {
 
    private static ThreadLocal<Integer> tl = new ThreadLocal<Integer>();  //private是為了安全,是一個普遍做法;static是因為這個變量有可能在static方法中使用
 
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            tl.set(1);
            tl.get();
            .........
        });
    }
}

 

 

 

理解了ThreadLocal的原理,使用起來很簡單,注意ThreadLocal對象的定義位置,檢查作用域,保證可以被要使用它的線程訪問到。

 

三. 關於Entry的弱類型引用

  如果閱讀ThreadLocalMap的Entry源碼會發現,Entry的key是弱引用:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);  //由於Entry繼承了WeakReference,所以這里以一個弱引用指向ThreadLcoal對象
                value = v;
            }
        }

為什么要這么做呢?看下面的這種場景:

public void func1() {
        ThreadLocal tl = new ThreadLocal<Integer>(); //line1
         tl.set(100);   //line2
         tl.get();       //line3
}

line1新建了一個ThreadLocal對象,t1 是強引用指向這個對象;line2調用set()后,新建一個Entry,通過源碼可知entry對象里的 k是弱引用指向這個對象。如圖:

 

當func1方法執行完畢后,棧幀銷毀,強引用 tl 也就沒有了,但此時線程的ThreadLocalMap里某個entry的 k 引用還指向這個對象。若這個k 引用是強引用,就會導致k指向的ThreadLocal對象及v指向的對象不能被gc回收,造成內存泄漏,但是弱引用就不會有這個問題(弱引用及強引用等這里不說了)。使用弱引用,就可以使ThreadLocal對象在方法執行完畢后順利被回收,而且在entry的k引用為null后,再調用get,set或remove方法時,就會嘗試刪除key為null的entry,可以釋放value對象所占用的內存。

概括說就是:在方法中新建一個ThreadLocal對象,就有一個強引用指向它,在調用set()后,線程的ThreadLocalMap對象里的Entry對象又有一個引用 k 指向它。如果后面這個引用 k 是強引用就會使方法執行完,棧幀中的強引用銷毀了,對象還不能回收,造成嚴重的內存泄露。

 

注意:雖然弱引用,保證了k指向的ThreadLocal對象能被及時回收,但是v指向的value對象是需要ThreadLocalMap調用get、set時發現k為null時才會去回收整個entry、value,因此弱引用不能保證內存完全不泄露。我們要在不使用某個ThreadLocal對象后,手動調用remoev方法來刪除它,尤其是在線程池中,不僅僅是內存泄露的問題,因為線程池中的線程是重復使用的,意味着這個線程的ThreadLocalMap對象也是重復使用的,如果我們不手動調用remove方法,那么后面的線程就有可能獲取到上個線程遺留下來的value值,造成bug。

 

四. 用途舉例

  從ThreadLocal類的特性就知道它的用途了,它可以看成專屬於線程的變量(實際上是通過它找到線程自己的某個Entry屬性對象),不受其他線程干擾,記錄着線程的某些信息。作用域比較特殊,它跟隨線程的一生,無論線程執行到哪個類的哪個方法,我都隨時可以用get()方法拿出來用。比如:在web后台中,可以將http請求的信息包裝到ThreadLocal對象 假設為tl,執行此請求的線程在開始前執行 tl.set(httpRequest),那么這個處理請求的線程無論執行到哪,都可以由tl.get()獲取當前的請求信息。

  Spring的RequestContextHolder就是這么操作的,這樣在使用切面時,也可以獲取到請求信息了(切面編程時自身是只可以獲取到方法名+方法參數信息的):


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM