ThreadLocal分析和使用

ThreadLocal是一個多線程情況下爲獨立線程存儲數據的類。
這樣說可能不太好理解,下面通過一個例子來看清晰明瞭:

private ThreadLocal<Integer> local = new ThreadLocal<>();
private int i;


@RequiresApi(api = Build.VERSION_CODES.N)
private void threadLocal() {
    IntStream.range(0, 5).forEach(value -> new Thread(() -> {
        local.set(value);
        i = value;
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread: " + value + ", local: " + local.get() + ", i: " + i);
        // 使用完要回收,以免內存泄漏
        local.remove();
    }).start());
}

打印結果是:

thread: 0, local: 0, i: 4
thread: 1, local: 1, i: 4
thread: 2, local: 2, i: 4
thread: 4, local: 4, i: 4
thread: 3, local: 3, i: 4

這裏我定義了一個普通int類型變量 i 和一個String類型ThreadLocal變量local,方法中創建了5個線程,線程編號就以value爲主,在每個線程中分別爲 i 和 local 賦值value,延遲2秒之後分別打印,從結果可以看到 i 的值是最後一次賦值的結果,而 local 是每個獨立的線程賦值的結果,不受其他線程影響。

瞭解ThreadLocal的這種特性,主要從他的set、get方法來看:

先看看set方法

public class ThreadLocal<T> {

    public void set(T value) {
        // 獲取此變量所在線程
        Thread t = Thread.currentThread();
                // 各個線程的值就存儲變量在各個線程的值
        ThreadLocalMap map = getMap(t);
        // 如果map存在
        if (map != null)
            // 當前線程作爲key,把value保存起來。
            map.set(this, value);
        else
            // 創建map
            createMap(t, value);
    }

    ThreadLocalMap getMap(Thread t) {
        // 這個其就是ThreadLocal中的一個靜態內部類
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
                // 這裏正好是getMap方法的返回對象,初始化一個ThreadLocalMap,並以當前線程爲key把value保存起來。
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
}
class Thread implements Runnable {

        ...

    ThreadLocal.ThreadLocalMap threadLocals = null;

    ...
}

接下來我們來看看ThreadLocalMap:

public class ThreadLocal<T> {

    // ThreadLocalMap是ThreadLocal的靜態內部類
    static class ThreadLocalMap {        // ThreadLocalMap保存數據最終是在Entry中,key是當前線程,value是Object值(其實就是ThreadLocal泛型T的值)        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }    }
}

然後是get方法

public class ThreadLocal<T> {
    public T get() {
        // 獲取當前所在線程
        Thread t = Thread.currentThread();
        // 得到map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 以當前線程爲key,獲取Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                // entry不爲空,拿到value返回。
                T result = (T)e.value;
                return result;
            }
        }
        // 否則就初始化一個值
        return setInitialValue();
    }

    // 初始化一個值(initialValue()方法返回null),並保存下來
    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;
    }
}

總結:
1、每一個Thread維護者一個ThreadLocalMap的引用,getMap方法提現;
2、Thread是通過ThreadLocal來操作變量的,創建的副本變量(vn)存儲在每個線程的threadLocals中,也就是ThreadLocalMap;
3、ThreadLocal本身並不存儲數據,它只是作爲ThreadLocalMap存儲數據的key;
在這裏插入圖片描述注意內存泄漏問題:ThreadLocalMap中的Entry是持有ThreadLocal對象的弱引用(Entry的key),當發生GC時ThreadLocal會被回收,這時候key沒有了,但是value還在(人s了,錢沒花了,氣人不),key不在了說明這個value不可能在被存取了,默默的躺在內存中沒有一點用,所以在使用完及時調用remove方法避免內存泄漏。

ThreadLocal和 Synchronized區別:
ThreadLocal和Synchonized都能用於解決多線程併發訪問時線程安全問題。
1、 Synchronized是通過鎖機制,犧牲時間爲代價,多線程間數據共享;
2、ThreadLocal是給每個線程分配單獨的存儲空間,犧牲空間爲代價,多線程間數據獨立(線程隔離);
當某些數據是以線程爲作用域且不同線程具有不同的數據時可以使用ThreadLocal。

個人學習體會,歡迎探討。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章