Android:ThreadLocal源碼解析

1、前言

  • 最初看到ThreadLocal這個東西是在Handler消息機制的Looper實例化的時候, 系統(tǒng)把Looper的實例對象保存在ThreadLocal里,當(dāng)有需要的時候就直接拿出來用,以此保證一個線程只有一個Looper對象;
  • 這篇文章就是對ThreadLocal源碼進行解析,記錄其工作過程與原理;

2、定義

先來看看系統(tǒng)源碼對其的一個簡單描述:

/**
 * Implements a thread-local storage, that is, a variable for which each thread
 * has its own value. All threads share the same {@code ThreadLocal} object,
 * but each sees a different value when accessing it, and changes made by one
 * thread do not affect the other threads. The implementation supports
 * {@code null} values.
 *
 * @see java.lang.Thread
 * @author Bob Lee
 */
public class ThreadLocal<T> {
    ···
    ···
}

觀察最上面的注釋,大概的意思是ThreadLocal是每個線程的成員變量,實現(xiàn)線程本地的存儲,所有線程都共享同一個對象,但是訪問的時候卻具有不同的值,實際就是在不同的線程中提供一份各自的副本,這樣線程與線程之間的數(shù)據(jù)就不會相互影響,是獨立存在的;
這句話是什么意思呢,舉個栗子:

public class MainActivity extends AppCompatActivity {
    private ThreadLocal<String> mThreadLocal = new ThreadLocal<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mThreadLocal.set("A");
        Log.i("測試","result->"+mThreadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                mThreadLocal.set("B");
                Log.i("測試","result->"+mThreadLocal.get());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i("測試","result->"+mThreadLocal.get());
            }
        }).start();

    }

首先我們定義了一個全局的ThreadLocal對象,然后分別在三個不同的線程中存入不同的值,然后得出的結(jié)果是:

07-08 14:01:38.149 19251-19251/com.qinkl.demo I/測試: result->A
07-08 14:01:38.151 19251-19294/com.qinkl.demo I/測試: result->B
07-08 14:01:38.168 19251-19295/com.qinkl.demo I/測試: result->null

在不同的線程中全局變量ThreadLocal的值是都是不同的,通過這個例子很好的證明了系統(tǒng)源碼的描述:多個線程共享同一個變量,但是訪問的值是不同的,只有一個解釋,就是在不同的線程中產(chǎn)生了各自的副本,在進行數(shù)據(jù)操作的時候,是互相獨立的,線程與線程之間不會有影響;
要知道ThreadLocal為什么會產(chǎn)生這樣的效果,就要從源碼入手,下面我們就開始探究其原理是怎樣的。


3、源碼解析

首先我們從ThreadLocalset方法開始:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

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

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

首先是獲得當(dāng)前所在的Thread對象,然后作為參數(shù)傳入getMap方法,在getMap方法中,返回的是當(dāng)前線程的以ThreadLocal.ThreadLocalMap聲明的threadLocals變量,判斷如果這個值不為空的話就直接存入ThreadLocalMap中,如果為空的話就調(diào)用createMap方法,該方法實例化了一個ThreadLocalMap對象并且賦值給線程的threadLocals變量,在構(gòu)造函數(shù)里面保存這個值;
ThreadLocalMap是什么呢:

static class ThreadLocalMap {
         
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        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;
        ···
}

ThreadLocalMap對象是ThreadLocal的靜態(tài)內(nèi)部類,內(nèi)部通過一個Entry[]數(shù)組存儲數(shù)據(jù),并且該數(shù)組的keyThreadLocal的弱引用,設(shè)計的目的是防止Entry[]持有外部類ThreadLocal引用導(dǎo)致不能回收的內(nèi)存泄露;
每個線程都持有一個自己的ThreadLocalMap,也就是持有自己的獨立副本,數(shù)據(jù)的操作都是在
ThreadLocalMap的數(shù)組完成的,所以保證了線程與線程之間不會有影響;
然后再看看ThreadLocalget方法:

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

protected T initialValue() {
        return null;
    }

get方法中,主要還是判斷當(dāng)前線程是否存在ThreadLocalMap對象,然后從Entry[]數(shù)組中取出該ThreadLocal對應(yīng)的value值,如果沒有的話,在setInitialValue方法中,設(shè)置為null的初始值;


4、總結(jié)

  • 雖然每個線程都共享同一對象,但是會在線程各自創(chuàng)建自己的副本,該副本是ThreadLocalMap對象;
  • ThreadLocal本質(zhì)是操作線程中ThreadLocalMap對象,而不是其本身;
  • ThreadLocalMap對象內(nèi)部維護了Entry[]數(shù)組存儲數(shù)據(jù),并且key為弱引用,目的是防止內(nèi)存泄漏;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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