Android ThreadLocal 就是孫大圣

CHANGE LOG

  • v0.1 2018/07/17 Chuck Chan

示例

我們先來看 ThreadLocal 的一個操作示例。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ThreadLoacalTest";
    private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init(){
        mBooleanThreadLocal.set(true);
        printThreadLocal();

        new Thread("Thread#1"){
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                printThreadLocal();
            }
        }.start();

        new Thread("Thread#2"){
            @Override
            public void run() {
                printThreadLocal();
            }
        }.start();
    }

    private void printThreadLocal() {
        Log.d(TAG, "[Thread#" + Thread.currentThread().getName() + "#" + Thread.currentThread().getId() + "]mBooleanThreadLocal=" + mBooleanThreadLocal.get() );
    }
}

以下是結(jié)果

07-17 11:23:19.773 3162-3162/com.chuckchan.threadlocalsample D/ThreadLoacalTest: [Thread#main#2]mBooleanThreadLocal=true
07-17 11:23:19.777 3162-3181/com.chuckchan.threadlocalsample D/ThreadLoacalTest: [Thread#Thread#2#151]mBooleanThreadLocal=null
07-17 11:23:19.798 3162-3180/com.chuckchan.threadlocalsample D/ThreadLoacalTest: [Thread#Thread#1#150]mBooleanThreadLocal=false

這個示例在3個不同的線程中,分別對同一個 ThreadLocal 對象進(jìn)行了操作,但結(jié)果互不干擾。

ThreadLocal 是什么?

ThreadLocal 是什么?簡單來說就是負(fù)責(zé)向線程內(nèi)進(jìn)行數(shù)據(jù)存儲/讀取操作的類。數(shù)據(jù)以線程為作用域。

以下是線程內(nèi)部用來存儲的成員變量

public class Thread implements Runnable {
    // 省略部分代碼
    ...

    /**
     * Normal thread local values.
     */
    ThreadLocal.Values localValues;
    
    // 省略部分代碼
    ...
}

ThreadLocal.Values 是什么?讓我們來看看他的成員變量

static class Values {

    /**
     * Size must always be a power of 2.
     */
    private static final int INITIAL_SIZE = 16;

    /**
     * Placeholder for deleted entries.
     */
    private static final Object TOMBSTONE = new Object();

    /**
     * Map entries. Contains alternating keys (ThreadLocal) and values.
     * The length is always a power of 2.
     */
    private Object[] table;

    /** Used to turn hashes into indices. */
    private int mask;

    /** Number of live entries. */
    private int size;

    /** Number of tombstones. */
    private int tombstones;

    /** Maximum number of live entries and tombstones. */
    private int maximumLoad;

    /** Points to the next cell to clean up. */
    private int clean;
    
    // 以下代碼省略
    ...
}

Object[] table 這就是存儲的核心,一個數(shù)組。

實現(xiàn)原理

首先提一個問題,ThreadLocal 為什么能以為線程為作用域進(jìn)行數(shù)據(jù)存儲?

我們分析應(yīng)該是以下2個方面:

  1. 他能拿到對應(yīng)的Thread 對象
  2. Thread 可以存數(shù)據(jù)

我們繼續(xù)分析,上一節(jié)我們已經(jīng)介紹主要有4種對象/數(shù)據(jù)結(jié)構(gòu)參與實現(xiàn)了以上2個功能,他們是

  • ThreadLocal
  • Thread
  • ThreadLocal.Values localValues
  • Object[] table

為了更容易記憶和分析,我們來做一個比喻。

大家都知道《西游記》中的孫大圣,每到到一個地方,呼喚一聲,這個地方的土地公就出來了,然后問土地公查一些資料,再翻個筋斗云,換個地方,呼喚一聲,又換了個土地公。

下面分配角色:

  • ThreadLocal 對象就是孫大圣
  • Thread 對象就是土地公
  • ThreadLocal.Values localValues 就是土地公的記事本
  • Object[] table 就是記事本里面的頁,通過頁數(shù)可以進(jìn)行查找。

表演的時刻到了:

  • 孫大圣(ThreadLocal )在女兒國領(lǐng)地內(nèi),忽然想起一點事情,但又怕忘了,得找地方記下來,比如他師傅又被抓了。他喊一聲“土地”(Thread currentThread = Thread.currentThread();),當(dāng)?shù)赝恋毓?code>Thread currentThread )就出現(xiàn)了,孫大圣的脾氣比較急,直接一把抓過土地公的記事本(Values values = values(currentThread);),往記事本中寫下這件事情(values.put(this, value);)。

    以上的過程就在ThreadLocal 中:

    /**
         * Sets the value of this variable for the current thread. If set to
         * {@code null}, the value will be set to null and the underlying entry will
         * still be present.
         *
         * @param value the new value of the variable for the caller thread.
         */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
    
/**
     * Gets Values instance for this thread and variable type.
     */
Values values(Thread current) {
    return current.localValues;
}

在記事本中,他是怎樣記錄的呢?以孫大圣的行事風(fēng)格, 他翻開記事本,在一頁空白處,寫上自己的大名“齊天大圣”(table[index] = key.reference;),再在下一頁記下內(nèi)容(table[index + 1] = value;)。

以下是values.put(this, value); 的具體實現(xiàn):

/**
         * Sets entry for given ThreadLocal to given value, creating an
         * entry if necessary.
         */
void put(ThreadLocal<?> key, Object value) {
    cleanUp();

    // Keep track of first tombstone. That's where we want to go back
    // and add an entry if necessary.
    int firstTombstone = -1;

    for (int index = key.hash & mask;; index = next(index)) {
        Object k = table[index];

        if (k == key.reference) {
            // Replace existing entry.
            table[index + 1] = value;
            return;
        }

        if (k == null) {
            if (firstTombstone == -1) {
                // Fill in null slot.
                table[index] = key.reference;
                table[index + 1] = value;
                size++;
                return;
            }

            // Go back and replace first tombstone.
            table[firstTombstone] = key.reference;
            table[firstTombstone + 1] = value;
            tombstones--;
            size++;
            return;
        }

        // Remember first tombstone.
        if (firstTombstone == -1 && k == TOMBSTONE) {
            firstTombstone = index;
        }
    }
}
  • 孫大圣(ThreadLocal )回了趟花果山,又一個筋斗云回到女兒國領(lǐng)地內(nèi)。他想起還有事情沒完成,喊一聲“土地”(Thread currentThread = Thread.currentThread();),當(dāng)?shù)赝恋毓?code>Thread currentThread )就出現(xiàn)了,抓過土地公的記事本(Values values = values(currentThread);),翻開記事本(Object[] table = values.table;),找到自己之前寫下“齊天大圣”的那一頁(if (this.reference == table[index])),下一頁就是上次記下的內(nèi)容(return (T) table[index + 1];)。

    以上的過程就在ThreadLocal 中:

    /**
         * Returns the value of this variable for the current thread. If an entry
         * doesn't yet exist for this variable on this thread, this method will
         * create an entry, populating the value with the result of
         * {@link #initialValue()}.
         *
         * @return the current value of the variable for the calling thread.
         */
    @SuppressWarnings("unchecked")
    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
    
        return (T) values.getAfterMiss(this);
    }
    
android_thread_local_sunwukong.png

再提一個問題,為什么是使用Object[] table ?

public class ThreadLocal<T> 從以上的代碼中看,他的 index 應(yīng)該是固定的,內(nèi)容 T 存在 index + 1 位置。那我們?yōu)槭裁从?Object[] ,直接 Object 就行了。

答案是,誰說了 ThreadLocal 只有一個,除了 ThreadLocal<Boolean> mBooleanThreadLoca ,我們還可以設(shè)置 ThreadLocal<Integer> mIntegerThreadLocal ... 正如孫大圣也可能有好幾個,真假美猴王,土地公可是分不清楚的,他們都能往上面寫。不過孫大圣們得記住自己寫下大名的位置( int index = key.hash & mask; ),這樣下次才能找到。

作用和應(yīng)用場景

  • 作用

    通過 ThreadLocal 可以在指定的線程中存儲數(shù)據(jù),數(shù)據(jù)存儲以后,只有在指定的線程中才可以獲取到存儲的數(shù)據(jù)。

  • 應(yīng)用場景:

    • 某些數(shù)據(jù)是以線程為作用域并且不同的線程具有不同的數(shù)據(jù)副本的時候,例如 Looper,ActivityThread,AMS
    • 復(fù)雜邏輯下的對象傳遞,比如監(jiān)聽器的傳遞。(這個場景,作者還沒實踐,不了解是什么情況,大家自己想象/實踐吧)

參考文檔

  • 《Android 開發(fā)藝術(shù)探索》,作者:任玉剛
?著作權(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)容

  • ThreadLocal和線程同步機制相比:都是為了解決多線程中相同變量的訪問沖突問題。在同步機制中,通過對象的鎖機...
    tiancijiaren閱讀 443評論 0 1
  • /** * This class provides thread-local variables. These ...
    大風(fēng)過崗閱讀 281評論 0 0
  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類: pyspark.sql...
    mpro閱讀 9,914評論 0 13
  • Android中的Handler一般是用于異步任務(wù),和Handler相關(guān)的一些概念有Looper,MessageQ...
    codingwz閱讀 353評論 0 0
  • 為啥要寫這篇文章 起初我看Handler相關(guān)源碼,看到Looper里面有個ThreadLocal,如下,而這個Th...
    xufang2閱讀 2,763評論 4 51

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