說起Android消息機(jī)制,老生常談的問題,Handler ,Looper,MessageQueue,Message這幾個(gè)都是離不開的話題,不同的類承載不同的功能,首先還是簡(jiǎn)單的總結(jié)下各自的功能:
Handler:發(fā)送和處理消息
MessageQueue:消息隊(duì)列,采用單鏈表結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)
Looper:調(diào)用loop()輪詢消息
Message:需要發(fā)送的內(nèi)容,消息
畫一張流程圖顯示:
當(dāng)然上邊只是簡(jiǎn)單的分析了下 Handler一個(gè)發(fā)送和處理的一個(gè)流程,只是讓大家溫故下,下面正式的開始介紹這個(gè)ThreadLocal 重量級(jí)角色:
ThreadLocal 介紹:(針對(duì)JDK 1.7闡述)
定義:早在JDK 1.2的版本中就提供java.lang.ThreadLocal疤祭,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路缓呛。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫出優(yōu)美的多線程程序,百度百科一下就知道,當(dāng)然這只是定義,先上一段代碼瞧瞧:
private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();..
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
booleanThreadLocal.set(true);
booleanThreadLocal.set(false);
Log.i(TAG, "run: ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get());
new Thread("Thread1"){
@Override
public void run() {
booleanThreadLocal.set(true);
Log.i(TAG, "run: Thread1+ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get());
}
}.start();
new Thread("Thread2"){
@Override
public void run() {
Log.i(TAG, "run: Thread2+ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get());
}
}.start();
}
});
代碼很簡(jiǎn)單就是給一個(gè)button設(shè)置一個(gè)點(diǎn)擊監(jiān)聽事件,然后通過獲取ThreadLocal的get方法分別獲取里面值:我們來看打印值(界面過于簡(jiǎn)單,就不在展示):
11-24 06:35:23.683 13691-13691/com.example.administrator.dbhelp I/MainActivity: run: ThreadName-------->main---->false
11-24 06:35:23.693 13691-13785/com.example.administrator.dbhelp I/MainActivity: run: Thread2+ThreadName-------->Thread2---->null
11-24 06:35:23.694 13691-13784/com.example.administrator.dbhelp I/MainActivity: run: Thread1+ThreadName-------->Thread1---->true
我在不同的線程采用相同的對(duì)象調(diào)用他們的get方法,但是他們打印的值卻是不一樣,很奇妙吧,這也就是他的奇妙之處.
首先簡(jiǎn)答的介紹為什么會(huì)產(chǎn)生這種效果,方便后邊更好的看代碼,ThreadLocal 內(nèi)部維護(hù)了一個(gè)針對(duì)每一個(gè)線程的數(shù)組Entry[],它的初始容量是16,我們?cè)谠O(shè)置value的時(shí)候?qū)?dāng)前的value值封裝Entry類里面,在然后再根據(jù)當(dāng)前的ThreadLocal的索引去查中對(duì)應(yīng)的Entry值,最終根據(jù)Entry對(duì)象取出value值,很明顯每個(gè)線程的數(shù)組是不相同,所以就可以取出不同的Entry,這么說肯定還是不是特別明白,沒關(guān)系這個(gè)只是開胃菜,提前有個(gè)了解有印象就行;上代碼一看就明了:
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);
}
邏輯很簡(jiǎn)單,創(chuàng)建當(dāng)前的線程根據(jù)當(dāng)前的線程獲取ThreadLocalMap 實(shí)例對(duì)象,這個(gè)ThreadLocalMap 是個(gè)什么了看這個(gè)跟HashMap 很相似啊,可別只看表面,這個(gè)可是沒有一點(diǎn)親戚關(guān)系,這點(diǎn)可別誤解了,接著看得到當(dāng)前線程的map 第一次獲取肯定是空:
我們看下getMap做了什么:
Thread 屬性;
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
找到這個(gè)變量第一次肯定是空, createMap(t, value);接著看
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
變量賦值操作,看下ThreadLocalMap的構(gòu)造:
private static final int INITIAL_CAPACITY = 16;
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
很簡(jiǎn)單的吧,首先構(gòu)造一個(gè)數(shù)組也就是我上邊提到的Entry數(shù)組初始容量16,然后通過按位運(yùn)算得到一個(gè)變量i值,構(gòu)造一個(gè)Entry對(duì)象將ThreadLocal和Value放進(jìn)去,將設(shè)置進(jìn)去的值包裝成一個(gè)對(duì)象存進(jìn)數(shù)組里邊,獲取的時(shí)候在通過數(shù)組的index取出當(dāng)前的對(duì)象,這樣一分析是不是有種明白的感覺別急,接著看構(gòu)造;
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
這個(gè)實(shí)體繼承WeakReference(就是我們常說的弱引用) v就是我們?cè)O(shè)置進(jìn)去的value,現(xiàn)在整體的流程是不是很清楚了,然后我們?cè)谀X補(bǔ)一下,值是這樣設(shè)置就去了,那么我獲取的時(shí)候是不是只要得到map也就是ThreadLocal.ThreadLocalMap threadLocals這個(gè)變量,再根據(jù)這個(gè)變量得到當(dāng)前線程的的數(shù)組,在通過數(shù)組的index是不是就可以得到當(dāng)前的對(duì)象,再根據(jù)的對(duì)象取出value,思路是沒有錯(cuò),我們看下源碼是不是這個(gè)樣子了
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();
}
是不是一模一樣了,其實(shí)它的源碼還是比較簡(jiǎn)單的,當(dāng)然還有更高級(jí)的用法下邊在上代碼:
private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<Boolean>(){
@Override
protected Boolean initialValue() {
return true;
}
};
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick: booleanThreadLocal------->"+booleanThreadLocal.get());
}
});
11-24 08:02:02.332 26018-26018/com.example.administrator.dbhelp I/MainActivity: onClick: booleanThreadLocal------->true
重寫initialValue值返回true,這次我沒有設(shè)置值,確返回true,是不是和get方法有關(guān),當(dāng)我們我們?cè)O(shè)置值的時(shí)候當(dāng)前的線程的map為null,這時(shí) 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;
}
是不是一樣的簡(jiǎn)單,邏輯很清楚,我們直接重寫了initialValue(),所以返回的value值就是我們重寫方法的返回值;到此源碼也就分析完畢了,這時(shí)我們?cè)倩剡^頭來看下Handler消息機(jī)制,Handler通過Looper.loop()方法輪詢消息,我們一般寫Handler的時(shí)候都是在主線程創(chuàng)建的,在程序啟動(dòng)的時(shí)候也就是加載ActivityThread類的時(shí)候系統(tǒng)已經(jīng)幫我們自動(dòng)的創(chuàng)建了主線程的Looper,通過上邊的圖也可知,可是如果我想在子線程創(chuàng)建Handler了,子線程是不是也需要自己的Looper;是不是自己也需要啟動(dòng)Looper.loop來循環(huán)消息,這時(shí)候ThreadLocal這個(gè)類就來了,ThreadLocal為當(dāng)前的每一個(gè)線程存儲(chǔ)一個(gè)Looper,每一個(gè)Looper也有唯一的一個(gè)消息隊(duì)列MessageQueue,所以在子線程new Handler()的時(shí)候需要我們手動(dòng)的去獲取當(dāng)前線程的Looper,主動(dòng)的調(diào)用Looper.prepare();
public static void prepare() {
prepare(true);
}
--------------------------------------------
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
-------------------------------------------
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
獲取當(dāng)前線程的Looper和MessageQueue
new Thread(new Runnable() {
private Handler handler;
@Override
public void run() {
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "handleMessage: "+Thread.currentThread().getName()+"workThread線程收到消息了-->");
}
};
handler.sendEmptyMessage(0x10000);
Looper.loop();
}
}).start();
----------------------------------------
或者來個(gè)暴力點(diǎn)的既然主線程已經(jīng)有了Looper了就用他已經(jīng)創(chuàng)建的好的
new Thread(new Runnable() {
private Handler handler;
@Override
public void run() {
handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "handleMessage: "+Thread.currentThread().getName()+"main線程收到消息了-->");
}
};
handler.sendEmptyMessage(0x10000);
}
}).start();
上邊兩個(gè)方法都可以使Handler 在子線程去處理,但是接受消息的結(jié)果當(dāng)然也是不一樣的,上邊的采用的子線程Looper,下邊是main線程Looper;
總結(jié):
其實(shí)在開發(fā)中用的ThreadLocal的地方極少,但是ThreadLocal也是不可忽視的一個(gè)重要點(diǎn),在面試的時(shí)候你能把ThreadLocal和Looper結(jié)合起來一起講,也許就是加分項(xiàng),再者ThreadLocal在某些特殊的場(chǎng)景的,通過它可以實(shí)現(xiàn)一個(gè)看起來比較復(fù)雜的功能,當(dāng)某些數(shù)據(jù)以線程為作用域并且不同線程要獲取不同數(shù)據(jù)的時(shí)候,就可以用到ThreadLocal;