安卓ThreadLocal是到底什麼?

    首先ThreadLoacl是什麼?之前看有些博客發現會有人這麼介紹ThreadLoacl:

它可以解決線程併發問題                    
它可以解決線程共享數據問題。。。
。。。。

    百事不得姐的我決定翻閱源碼進行一探究竟!

首先看看怎麼用
public class MainActivity extends AppCompatActivity {
    ThreadLocal<String> threadLocal = new ThreadLocal<String>(){

        @Override
        protected String initialValue() {
            return "null";
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        for (int i = 0; i < 10; i++) {
            new myThread().start();
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class myThread extends Thread {
        @Override
        public void run() {
            Log.e(Thread.currentThread().getName(),threadLocal.get());
            threadLocal.set(Thread.currentThread().getName());
            Log.e(Thread.currentThread().getName(),threadLocal.get());
        }
    }
}

    首先我們創建一個ThreadLocal對象,然後創建十個線程,每個線程將線程名稱存儲在threadloacl中,然後我們在存儲前後打印出來對應的get值,結果如下:

2019-01-02 14:04:25.734 16502-16530/com.lxf.myapplication E/Thread-4: null
2019-01-02 14:04:25.734 16502-16530/com.lxf.myapplication E/Thread-4: Thread-4
2019-01-02 14:04:25.937 16502-16531/com.lxf.myapplication E/Thread-5: null
2019-01-02 14:04:25.937 16502-16531/com.lxf.myapplication E/Thread-5: Thread-5
2019-01-02 14:04:26.137 16502-16532/com.lxf.myapplication E/Thread-6: null
2019-01-02 14:04:26.137 16502-16532/com.lxf.myapplication E/Thread-6: Thread-6
2019-01-02 14:04:26.340 16502-16533/com.lxf.myapplication E/Thread-7: null
2019-01-02 14:04:26.340 16502-16533/com.lxf.myapplication E/Thread-7: Thread-7
2019-01-02 14:04:26.544 16502-16534/com.lxf.myapplication E/Thread-8: null
2019-01-02 14:04:26.544 16502-16534/com.lxf.myapplication E/Thread-8: Thread-8
2019-01-02 14:04:26.744 16502-16535/com.lxf.myapplication E/Thread-9: null
2019-01-02 14:04:26.744 16502-16535/com.lxf.myapplication E/Thread-9: Thread-9
2019-01-02 14:04:26.947 16502-16536/com.lxf.myapplication E/Thread-10: null
2019-01-02 14:04:26.947 16502-16536/com.lxf.myapplication E/Thread-10: Thread-10
2019-01-02 14:04:27.149 16502-16537/com.lxf.myapplication E/Thread-11: null
2019-01-02 14:04:27.149 16502-16537/com.lxf.myapplication E/Thread-11: Thread-11
2019-01-02 14:04:27.350 16502-16538/com.lxf.myapplication E/Thread-12: null
2019-01-02 14:04:27.350 16502-16538/com.lxf.myapplication E/Thread-12: Thread-12
2019-01-02 14:04:27.552 16502-16539/com.lxf.myapplication E/Thread-13: null
2019-01-02 14:04:27.553 16502-16539/com.lxf.myapplication E/Thread-13: Thread-13

結果在我們的預想中

然後我們看一下源碼到底是怎麼回事

首先我們看一下set方法做了什麼事情

    public void set(T value) {
        //首先去拿一下當前調用的線程
        Thread t = Thread.currentThread();
       //這裏拿到一個ThreadLocalMap 
        ThreadLocalMap map = getMap(t);
      //ThreadLocalMap 不爲null的時候將其設置進去
        if (map != null)
            map.set(this, value);
      //否則創建一個map
        else
            createMap(t, value);
    }

這裏我們可以看到set的一個流程就是將參數傳入的value設置到了一個ThreadLocalMap 中。那麼這個ThreadLocalMap 是什麼呢?

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

我們發現這裏直接返回了當前調用線程的一個變量threadLocals,

   /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

Thread中我們發現初始值是null

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

然後我們發現在creatMap中初始化了這個變量,默認的第一個鍵值對就是當前初始化這個變量的 ThreadLocal實例和要外界線程中存入ThreadLocal的值。

ThreadLocalMap是ThreadLocal中的一個內部類,
這裏我們發現map中的鍵值對是存放在一個table數組中的,並且對Entry做了弱引用,這樣的話當我們的線程釋放之後,內部的ThreadLoacl中的數據也會被回收。以防出現內存泄露的情況!

    private Entry[] table;
     static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
.....................................................................................................................................
     ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

這裏的map和hashmap 的實現有所不同,ThreadLocal內部維護着一個threadLocalHashCode ,是根據這個threadLocal的threadLocalHashCode 來計算出一個數組的存放索引!這是一個自定義計算出來的hashcode,消除避免了哈希碰撞的情況!所以計算出來的索引都是唯一的!
然後我們看看這個map的set方法

           private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

上面的方法我們可以看到,其實就是做了一系列的判斷,判空,替換,數組擴容等操作,本質就是將要存放的value以threadlocal爲key存放起來。

然後我們看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();
    }

很明瞭,threadlocal會根據當前調用的線程來獲取當前線程中的threadlocalmap,然後從中取出key爲該threadlocal的value,這個value就是之前存入的值。
    那麼通過源碼的分析,我們發現threadlocal並不能實現上面的併發問題,因爲實際上根本就沒有併發相關的問題。存入threadlocal中的數據實際上是存入了對應的調用線程中的內部的一個map中,與threadlocal相關的實際上只是將其作爲一個key引用起來,作爲取值的憑證而已。同理我們發現threadlocal的做法也並沒有實現數據線程間共享的操作,相反是做到了數據的線程間隔離,各個線程只能訪問各個線程的數據,做到了線程的數據安全。
    實際上我們發現 ThreadLocal可以做到:

1,數據的線程內全局使用,維持了代碼的簡潔,避免了大量的函數傳值
2,做到了線程與線程的數據隔離,使得線程的數據變得安全,不用考慮多個線程操作同一數據造成的併發安全問題。
3,內部使用弱引用解決了可能出現的內存泄露問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章