因為在android中經(jīng)常用到
handler
來處理異步任務(wù),通常用于接收消息但绕,來操作UIThread救崔,其中提到涉及到的looper
對象就是保存在Threadlocal
中的,因此研究下Threadlocal
的源碼捏顺。
分析都是基于android sdk 23 源碼進行的六孵,ThreadLocal在android和jdk中的實現(xiàn)可能并不一致。
在最初使用Threadlocal
的時候幅骄,很容易會產(chǎn)生的誤解就是threadlocal就是一個線程劫窒。
首先來看下Threadlocal
的簡單例子:
一個簡單的Person
類:
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
一個簡單的activity:
public class MainActivity extends Activity {
//ThreadLocal初始化
private ThreadLocal<Person> mThreadLocal = new ThreadLocal<Person>();
private Person mPerson = new Person("王大俠", 100);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//將mPerson對象設(shè)置進去
mThreadLocal.set(mPerson);
Log.d("主線程", " 名字:" + mThreadLocal.get().name + " 年齡:" + mThreadLocal.get().age);
}
}
運行看看是否能得到mperson對象:
04-19 13:14:31.053 2801-2801/com.example.franky.myapplication d/主線程: 名字:王大俠 年齡:100
果然得到了!說明當前線程確實已經(jīng)存儲了mPerson
對象的引用拆座。
那么我們開啟個子線程看看適合還能獲取到mPerson
對象呢:
public class MainActivity extends Activity {
//ThreadLocal初始化
private ThreadLocal<Person> mThreadLocal = new ThreadLocal<Person>();
private Person mPerson = new Person("王大俠", 100);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//將mPerson對象設(shè)置進去
mThreadLocal.set(mPerson);
new Thread(new Runnable() {
@Override
public void run() {
Log.d("子線程", " 名字:" + mThreadLocal.get().name + " 年齡:" + mThreadLocal.get().age);
}
}).start();
}
}
運行看看結(jié)果:
`java.lang.NullPointerException: Attempt to read from field 'java.lang.String com.example.franky.myapplication.Person.name' on a null object reference`
哈哈主巍,報錯信息很明顯,空指針異常挪凑,這清楚的表明子線程是獲取不到mperson對象的孕索,但可能到這里一些朋友可能有些暈了,明明我通過set()
方法將mperson設(shè)置給threadlocal對象了啊躏碳,為啥在這里get()
不到呢搞旭?
這里我們開始追蹤threadlocal的源碼看看有沒有線索來解釋這個疑問。
首先我們可以看看threadlocal的構(gòu)造方法:
/**
* Creates a new thread-local variable.
*/
public ThreadLocal() {}
構(gòu)造方法平淡無奇菇绵,那么我們瞅瞅threadlocal的類說明吧肄渗,看看有沒有發(fā)現(xiàn):
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.
個人英文其實不是很好,大致的意思是每個線程都能在自己的線程保持一個對象咬最,如果在一個線程改變對象的屬性不會影響其他線程翎嫡。但我們不要誤讀,如果某個對象是共享變量永乌,那么在某個線程中改變它時钝的,其他線程訪問的時候其實該對象也被改變了翁垂,所以并不是說ThreadLocal是線程安全的,我們只要理解ThreadLocal是能在當前線程保存一個對象的硝桩,這樣我們不用到處傳遞這個對象沿猜。
那ThreadLocal是線程嗎?其實看看threadlocal有沒有繼承thread類就知道了:
public class ThreadLocal<T> {
}
答案是沒有~~碗脊,這說明threadlocal并不是線程啼肩。
明白了這點,那我們繼續(xù)往下看看ThreadLocal
是如何將對象保存起來的衙伶,瞅瞅set()
方法:
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
首先通過Thread currentthread = thread.currentthread();
獲取到當前線程
然后currentthread
作為方法參數(shù)傳遞給了vlaues
方法:
Values values(Thread current) {
return current.localValues;
}
這里我們看到return的是thread類的一個成員變量祈坠,我們瞅瞅Thread
類中的這個變量:
ThreadLocal.Values localValues;
這里我們看到localvalues
成員變量的類型就是ThreadLocal.Values
這個類其實是ThreadLocal
的內(nèi)部類。
然后這里判斷得到的values
對象是不是null
矢劲,也就是說Thread
類中的成員變量localvalues
是不是null
赦拘,由于我們是初次設(shè)置,所以這個對象肯定是null
芬沉,那繼續(xù)走
values initializevalues(thread current) { return current.localvalues = new values();}
很明顯直接給localvalues
變量new
了一個value
對象躺同。那我們看看values
對象里有啥:
首先看看構(gòu)造:
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}
看起來是初始化了一些成員變量的值,INITIAL_SIZE
的值為16,
看看initializeTable(INITIAL_SIZE)
這個方法是做啥的:
private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}
初始化了長度為32的table
數(shù)組丸逸,mask
為31蹋艺,clean
為0,maximumLoad
為10黄刚。
又是一堆成員變量捎谨,那只好看看變量的說明是做啥的:
這個table
很簡單就是個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;
這樣看來mask
是用來計算數(shù)組下標的检吆,size
其實是存活的保存的對象數(shù)量,tombstones
是過時的對象數(shù)量凶赁,maximumLoad
是最大的保存數(shù)量咧栗,clean
是指向的下一個要清理的位置逆甜。大概明白了這些我們再繼續(xù)看:
values.put(this, value);
繼續(xù)追蹤:
/**
* 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;
}
}
}
該方法直接將this
對象和要保存的對象傳遞了進來虱肄,
第一行的cleanUp()
其實是用來對table
執(zhí)行清理的,比如清理一些過時的對象交煞,檢查是否對象的數(shù)量是否超過設(shè)置值咏窿,或者擴容等,這里不再細說素征,有興趣大家可以研究下集嵌。
然后利用key.hash&mask
計算下標萝挤,這里key.hash
的初始化值:
private static AtomicInteger hashCounter = new AtomicInteger(0);
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);
然后注釋說為了確保計算的下標指向的是key值而不是value,當然為啥用上述數(shù)值進行計算就能保證獲取的key值根欧,貌似是和這個0x61c88647
數(shù)值有關(guān)怜珍,再深入的大家可以留言。
然后最重要的就是
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
這里將自身的引用凤粗,而且是弱引用酥泛,放在了table[index]
上,將value
放在它的下一個位置嫌拣,保證key和value是排列在一起的柔袁,這樣其實我們知道了key其實是threadlocal
的引用,值是value
异逐,它們一同被放置在table
數(shù)組內(nèi)捶索。
所以現(xiàn)在的情況是這樣,Thread
類中的成員變量localValues
是ThreadLocal.Values
類型,所以說白了就是當前線程持有了ThreadLocal.Values
這樣的數(shù)據(jù)結(jié)構(gòu)灰瞻,我們設(shè)置的value全部都存儲在里面了腥例,當然如果我們在一個線程中new
了很多ThreadLocal
對象,其實指向都是Thread
類中的成員變量localValues
箩祥,而且如果new
了很多ThreadLocal
對象院崇,其實都是放在table
中的不同位置的。
那接下來看看get()
方法:
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);
}
代碼比較簡單了袍祖,首先還是獲取當前線程底瓣,然后獲取當前線程的Values
對象,也就是Thread
類中的成員變量localValues
蕉陋,然后拿到Values
對象的table
數(shù)組捐凭,計算下標,獲取保存的對象凳鬓,當然如果沒有獲取到return (T) values.getAfterMiss(this)
茁肠,就是返回null
了,其實看方法Object getAfterMiss(ThreadLocal<?> key)
中的這個代碼:
Object value = key.initialValue();
protected T initialValue() {
return null;
}
就很清楚了缩举,當然我們可以復(fù)寫這個方法來實現(xiàn)自定義返回垦梆,大家有興趣可以試試。
到此我們再回過頭來看看開始的疑問仅孩,為啥mThreadLocal
在子線程獲取不到mPerson
對象呢托猩?原因就在于子線程獲取自身線程中的localValues
變量中并未保存mPerson
,真正保存的是主線程辽慕,所以我們是獲取不到的京腥。
看完了ThreadLocal
我們再看看它的一個子類InheritableThreadLocal
,該類和ThreadLocal
最大的不同就是它可以在子線程獲取到保存的對象溅蛉,而ThreadLocal
只能在同一個線程公浪,我們看看簡單的例子:
public class MainActivity extends Activity {
private InheritableThreadLocal<Person> mInheritableThreadLocal = new InheritableThreadLocal<Person>();
private Person mPerson = new Person("王大俠", 100);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//將mPerson設(shè)置到當前線程
mInheritableThreadLocal.set(mPerson);
Log.d("主線程"+Thread.currentThread().getName(), " 名字:" + mInheritableThreadLocal.get().name + " 年齡:" + mInheritableThreadLocal.get().age);
new Thread(new Runnable() {
@Override
public void run() {
Log.d("子線程"+Thread.currentThread().getName(), " 名字:" + mInheritableThreadLocal.get().name + " 年齡:" + mInheritableThreadLocal.get().age);
}
}).start();
}}
運行看看輸出:
04-21 13:09:11.046 19457-19457/com.example.franky.myapplication D/主線程main: 名字:王大俠 年齡:100
04-21 13:09:11.083 19457-21729/com.example.franky.myapplication D/子線程Thread-184: 名字:王大俠 年齡:100
很明顯在子線程也獲取到了mPerson
對象他宛,那它是如何實現(xiàn)的呢?
看下源碼:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* Creates a new inheritable thread-local variable.
*/
public InheritableThreadLocal() {
}
/**
* Computes the initial value of this thread-local variable for the child
* thread given the parent thread's value. Called from the parent thread when
* creating a child thread. The default implementation returns the parent
* thread's value.
*
* @param parentValue the value of the variable in the parent thread.
* @return the initial value of the variable for the child thread.
*/
protected T childValue(T parentValue) {
return parentValue;
}
@Override
Values values(Thread current) {
return current.inheritableValues;
}
@Override
Values initializeValues(Thread current) {
return current.inheritableValues = new Values();
}
}
很明顯InheritableThreadLocal
重寫了兩個方法:
Values values(Thread current)
方法返回了Thread
類中的成員變量inheritableValues
欠气。
Values initializeValues(Thread current)
也是new
的對象也是指向inheritableValues
厅各。
而ThreadLocal
中都是指向的localValues
這個變量。
也就是說當我們調(diào)用set(T value)
方法時预柒,根據(jù)前面的分析讯检,其實初始化的是這個inheritableValues
,那么既然子線程能夠獲取到保存的對象卫旱,那我們看看這個變量在Thread
類中哪里有調(diào)用人灼,搜索下就看到:
private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
...
// Transfer over InheritableThreadLocals.
if (currentThread.inheritableValues != null) {
inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
}
// add ourselves to our ThreadGroup of choice
this.group.addThread(this);
}
在Thread
類中的create
方法中可以看到,該方法在Thread
構(gòu)造方法中被調(diào)用顾翼,如果currentThread.inheritableValues
不為空,就會將它傳遞給Values
的有參構(gòu)造:
/**
* Used for InheritableThreadLocals.
*/
Values(Values fromParent) {
this.table = fromParent.table.clone();
this.mask = fromParent.mask;
this.size = fromParent.size;
this.tombstones = fromParent.tombstones;
this.maximumLoad = fromParent.maximumLoad;
this.clean = fromParent.clean;
inheritValues(fromParent);
}
這里可以看到將inheritableValues
的值完全復(fù)制過來了投放,所以我們在子線程一樣可以獲取到保存的變量,我們的分析就到此為止吧适贸。
自己總結(jié)的肯定有很多紕漏灸芳,還請大家多多指正。