天天看點

ThreadLocal的get方法、set方法,以及其記憶體洩露問題

ThreadLocal的設計思想

既然多線程通路同一個變量會造成線程安全的問題,那麼建立出來一個變量,需要使用這個變量的線程将該變量拷貝一份,并且拷貝到每一個線程的變量是線程私有的,使得變量線上程之間隔離起來使用,避免了線程之間的交錯使用資料造成的線程安全問題。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部類,它中間有一個内部類 Entry。

// 将Entry的key設定為弱引用
	static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
		// 構造方法,ThreadLocal作為鍵,value作為值
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
           

ThreadLocalMap将Entry設定為弱引用,是為了防止記憶體洩露。

将key設定為弱引用,在下一次GC的時候,将弱引用指向的對象回收。在多線程中,假設多個線程使用同一個ThreadLocal類型的變量,也就是每個線程的ThreadLocalMap的其中一個Entry中的key使用的是同一個ThreadLocal類型的變量。

舉例

三個線程中的每個線程的 ThreadLocalMap 的其中一個 Entry 中的 key 使用的是同一個 ThreadLocal 類型變量的位址。都指向了 ThreadLocal1 。

此時假設是強引用:多個線程依賴同一個 ThreadLocal1 ,此時 線程 1 的 ThreadLocal1 使用結束了想要釋放記憶體,但是由于是強引用(因為還有其他線程還在指向 ThreadLocal1),這就導緻了線程1 的持有 ThreadLocal1 的 Entry 占有的記憶體無法釋放,導緻了記憶體洩露,使用弱引用的時候,這種問題就可以解決。

ThreadLocal中還設計了Entry數組來存放多個Entry。

使用ThreadLocal

下面是一段示範代碼,main函數作為線程,對ThreadLocal進行操作。

package com.school.service;

public class Main {
    public static void main(String[] args) {
        ThreadLocal t = new ThreadLocal();
        t.set("jqz");
        t.set("zy");
        String s = (String)t.get();
        System.out.println(s);
    }
}
           

set方法

點進set方法,進去為下方代碼,為其加上注釋。

// set方法,參數為要插入的值
	public void set(T value) {
        // 擷取對目前執行線程對象的引用。
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
           

點進getMap(t)這個方法,代碼如下

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
           

getMap方法參數為線程對象,可以看見傳回值為ThreadLocal,點進去t.threadLocals;

可以看到

這是在Thread類裡的,Thread類中内置了ThreadLocalMap這個類型的參數,可以發現每個線程都是有自己獨立的ThreadLocalMap對象。

ThreadLocal通過getMap方法擷取線程對象的ThreadLocal。

繼續向下看set方法。

// set方法,參數為要插入的值
	public void set(T value) {
        // 擷取對目前執行線程對象的引用
        Thread t = Thread.currentThread();
        // 擷取線程中的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        // 如果map裡面已經插入過值了
        if (map != null)
            // 對map中再set進去目前傳入的值,它的鍵為這個ThreadLocal對象
            map.set(this, value);
        // 如果map裡面沒有插入過值
        else
            // 建立新的ThreadLocalMap對象,插入值進去,它的鍵為這個ThreadLocal對象
            createMap(t, value);
    }
           

get方法

點進get方法,進去為下方代碼,為其加上注釋。

// get方法,傳回目前線程,在該ThreadLocal中的值
	public T get() {
        // 擷取對目前執行線程對象的引用
        Thread t = Thread.currentThread();
         // 擷取線程中的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        // 如果ThreadLocalMap存在
        if (map != null) {
            // 通過ThreadLocal作為鍵,擷取ThreadLocalMap的Entry對象
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 如果Entry不為空
            if (e != null) {
                // 擷取結果
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
           
private T setInitialValue() {
        // 将value設為空值
        T value = initialValue();
        // 擷取目前線程對象
        Thread t = Thread.currentThread();
        // 擷取目前線程的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        // 将控制設定進ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        // 放回空值
        return value;
    }
           

場景

如果有兩個線程使用相同的ThreadLocal,通路值會相同嗎?

如果相同的線程通路兩個不同的ThreadLocal,通路值會相同嗎?

隻有相同對象通路相同的ThreadLocal,通路值才會相同。