天天看點

ThreadLocal源碼分析以及解決常見面試問題

參考: http://www.cnblogs.com/dolphin0520/p/3920407.html 參考用例

https://www.cnblogs.com/ablejava/p/5914090.html 根本原因分析 

1,正常使用

static final ThreadLocal<Long> longLocal = new ThreadLocal<>();
    static final ThreadLocal<String> stringLocal = new ThreadLocal<>();
    // 被調用的 入口
    public void testThreadLocal(String threadNameA, String threadNameB) {
        set();
        new Thread(new Runnable() {
            @Override
            public void run() {
                set();
                get();
            }
        }, threadNameA).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                set();
                get();
            }
        }, threadNameB).start();
        get();
    }
	// 統一,放入資料
    private void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
	// 統一,擷取資料,并列印
    private void get() {
        long longNum = longLocal.get();
        String strName = stringLocal.get();
        LogUtil.v("longNum = " + longNum + ", strName = " + strName);
    }
           

列印結果:

V/xxx->MainActivity.get(L:57):: LogUtil -> longNum = 56015, strName = newThreadA
V/xxx->MainActivity.get(L:57):: LogUtil -> longNum = 56016, strName = newThreadB
V/xxx->MainActivity.get(L:57):: LogUtil -> longNum = 1, strName = main
           

總結:     * 以上測試代碼也是系統源碼中使用ThreadLocal的方式     * 以上測試結果,僅僅表明:ThreadLocal能夠避免線程安全問題,因為多線程操作并沒有産生沖突(多次嘗試列印結果一樣)

2,ThreadLocal的set、get方法

1) set方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // 擷取舊的ThreadLocalMap
        if (map != null)
            map.set(this, value); // 直接放置資料
        else
            createMap(t, value); // 建立新的ThreadLocalMap
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
	
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
           

set的作用為:若目前線程,無ThreadLocalMap,則建立并放置資料;若目前線程,有ThreadLocalMap,則繼續放入資料

2) get方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return 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;
    }
           

2) get方法

get的作用為:若目前線程,有ThreadLocalMap,則擷取其中的資料;若目前線程,無ThreadLocalMap,則擷取預設初始化的資料 總結:

    * ThreadLocal放置和擷取資料,都是以 this和value作為一對放入;是以,一個threadLocal,僅僅對應一個value

    * ThreadLocal<T> 中的泛型,和value的資料類型一緻     * ThreadLocal每次存放資料,都是放置到ThreadLocalMap中,而ThreadLocalMap是屬于Thread的私有變量

3,ThreadLocalMap

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private static final int INITIAL_CAPACITY = 16; // 初始容量
        private Entry[] table; // 具體存儲的資料
	}
           

總結:     * ThreadLocalMap和普通的map相同,是以數組作為實際的存儲對象

    * ThreadLocalMap中數組對應的是,Entry(ThreadLocal、Value),并且key和value是弱引用;即:一旦某一方被回收,另一方是不會導緻記憶體洩漏的

4,整體記憶體分析

1) 記憶體是以Thread為主,Thread持有ThreadLocalMap、ThreadLocalMap存放着(ThreadLocal<Value> - Value)這樣的數組 是以,當多線程環境下,對應的引用關系如下圖所示:

ThreadLocal源碼分析以及解決常見面試問題

結論:多線程環境下,ThreadLocal是靜态類型,是以多線程的key是相同的,但value是不同的;換句話說:value就是Thread的私有成員變量

5,依據以上分析解決常見的疑問

1) ThreadLocal 線程安全的原理     線程安全的核心在于:多線程在使用共享資料時,造成的沖突;是以,對于私有變量而言,自然而然就沒有線程安全的問題。     而value每次通過Thread擷取到的都是目前線程的私有變量,并沒有多線程共享該資料,當然沒有線程安全的問題

2) ThreadLocal 是否存在記憶體洩漏     ThreadLocalMap的存儲Entry{ThreadLocal - Value}關聯方式為弱引用,是以當某個ThreadLocal置空,gc時,Value是能夠被回收掉的;是以不存在記憶體洩漏問題

3) ThreadLocal 如何使用     * ThreadLocal一定是共享的,最好是static final類型,原因:ThreadLocal<T>,可以通過T來固定Value為單一類型;并且static final類型,能夠確定在記憶體中為一份且不可改變

    * Value值一定不能是static類型,若Value為static類型,ThreadLocal線程安全将失效

4) ThreadLocal 使用空間換時間,如何了解     * 通過增加數組(存放引用)以及每個線程備份Value的方式,解決線程安全的問題

demo位址:

ThreadLocal 測試Activity

2) get方法

繼續閱讀