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個方面:
- 他能拿到對應(yīng)的
Thread對象 -
Thread可以存數(shù)據(jù)
我們繼續(xù)分析,上一節(jié)我們已經(jīng)介紹主要有4種對象/數(shù)據(jù)結(jié)構(gòu)參與實現(xiàn)了以上2個功能,他們是
ThreadLocalThreadThreadLocal.Values localValuesObject[] 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); }

再提一個問題,為什么是使用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)聽器的傳遞。(這個場景,作者還沒實踐,不了解是什么情況,大家自己想象/實踐吧)
- 某些數(shù)據(jù)是以線程為作用域并且不同的線程具有不同的數(shù)據(jù)副本的時候,例如
參考文檔
- 《Android 開發(fā)藝術(shù)探索》,作者:任玉剛