ThreadLocal 的作用和實(shí)現(xiàn)原理

     ThreadLocal  類用來提供線程內(nèi)部的局部變量,并且這些變量依靠線程獨(dú)立存在.
     可以在多個(gè)線程中互不干擾的進(jìn)行存儲數(shù)據(jù)和修改數(shù)據(jù),通過set,get 和remove方法, 
     每個(gè)線程都是獨(dú)立的操作.

     里面的原理是:在不同的線程中訪問同一個(gè)對象,獲取到的值是不一樣的,因?yàn)閮?nèi)部會 
     從各種的線程中取出一個(gè)數(shù)組。 通過對應(yīng)的下標(biāo),查找對應(yīng)的Value值

下面看個(gè)簡單的列子:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        
        final ThreadLocal<String> threadLocal = new ThreadLocal<String>();
        threadLocal.set("123");
        Log.e("Charles2", "ss1==" + threadLocal.get());

        new Thread(new Runnable() {
            @Override
            public void run() {
                String s = threadLocal.get();
                Log.e("Charles2", "ss2=" + s);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("234");
                String s = threadLocal.get();
                Log.e("Charles2", "ss3==" + s);
            }
        }).start();
    }

打印的結(jié)果如下 :

07-18 15:58:14.312 21113-21113/? E/Charles2: ss1==123
07-18 15:58:14.326 21113-21140/? E/Charles2: ss2=null
07-18 15:58:14.327 21113-21141/? E/Charles2: ss3==234
 大家都知道這三行打印都在不同的線程,線程1是在主線程,線程2 和 線程3 分別在不各 
 自線程中,但是由于線程2沒有給它設(shè)置值所以取出來的是null.其它2個(gè)線程的內(nèi)部變量 
 都是有值.

二、實(shí)現(xiàn)原理
ThreadLocal的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);
    }

如上面的代碼看,得到map對象之后,用的this作為key,this在這里代表的是當(dāng)前線程的ThreadLocal對象。 另外就是第二句根據(jù)getMap獲取一個(gè)ThreadLocalMap,其中g(shù)etMap中傳入了參數(shù)t(當(dāng)前線程對象),這樣就能夠獲取每個(gè)線程的ThreadLocal了。 繼續(xù)跟進(jìn)到ThreadLocalMap中查看set方法:

(2)1.ThreadLocalMap
ThreadLocalMap是ThreadLocal的一個(gè)內(nèi)部類,在分析其set方法之前,查看一下其類結(jié)構(gòu)和成員變量。

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

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

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

然后看下ThreadLocal的構(gòu)造方法

        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);
        }

構(gòu)造函數(shù)的第一個(gè)參數(shù)就是本ThreadLocal實(shí)例(this),第二個(gè)參數(shù)就是要保存的線程本地變量。構(gòu)造函數(shù)首先創(chuàng)建一個(gè)長度為16的Entry數(shù)組,然后計(jì)算出firstKey對應(yīng)的哈希值,然后存儲到table中,并設(shè)置size和threshold。

注意一個(gè)細(xì)節(jié),計(jì)算hash的時(shí)候里面采用了hashCode & (size - 1)的算法,這相當(dāng)于取模運(yùn)算hashCode % size的一個(gè)更高效的實(shí)現(xiàn)(和HashMap中的思路相同)。正是因?yàn)檫@種算法,我們要求size必須是2的指數(shù),因?yàn)檫@可以使得hash發(fā)生沖突的次數(shù)減小。

ThreadLocalMap#set
ThreadLocal中put函數(shù)最終調(diào)用了ThreadLocalMap中的set函數(shù),跟進(jìn)去看一看:
···
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();
    }

···
在上述代碼中如果Entry在存放過程中沖突了,調(diào)用nextIndex來處理,如下所示。是否還記得hashmap中對待沖突的處理?這里好像是另一種套路:只要i的數(shù)值小于len,就加1取值,官方術(shù)語稱為:線性探測法。

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

以上步驟ok了之后,再次關(guān)注一下源碼中的cleanSomeSlots,該函數(shù)主要的作用就是清理無用的entry,具體細(xì)節(jié)就不扣了:

        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

下面來看ThreadLocal#get

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

如果map不會空,就根據(jù)當(dāng)前線程去查存儲的變量。
如果map為null,就返回setInitialValue()這個(gè)方法,跟進(jìn)這個(gè)方法看一下:

    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;
    }

最后返回的是value,而value來自initialValue(),進(jìn)入這個(gè)源碼中查看:

    protected T initialValue() {
        return null;
    }

原來如此,如果不設(shè)置ThreadLocal的數(shù)值,默認(rèn)就是null,來自于此。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容