Java中的線程回顧
在操作系統(tǒng)的概念里,進(jìn)程是具有一定獨(dú)立功能的程序、它是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。
在Java的虛擬機(jī)中,進(jìn)程擁有自己獨(dú)立的虛擬地址空間(準(zhǔn)確的說(shuō)是方法區(qū)和堆內(nèi)存)。
而線程則是CPU調(diào)度的基本單元,它沒(méi)有自己獨(dú)立的虛擬地址空間,它依附于進(jìn)程的虛擬地址空間。在Java虛擬機(jī)中,線程擁有的不過(guò)是PC計(jì)數(shù)器和方法棧而已。
在Android系統(tǒng)中,進(jìn)程之間的通信稱為IPC,它主要由Binder完成,線程之間由于共享地址空間,各線程都可以對(duì)對(duì)象進(jìn)行操作,因此線程間的通信機(jī)制更為簡(jiǎn)單,由輕便的Handler和Looper完成。
線程獨(dú)有數(shù)據(jù)
由于同一個(gè)進(jìn)程的線程共享資源,因此,如何保證數(shù)據(jù)在某線程的私有性變?yōu)闃O為重要,忽略這點(diǎn)會(huì)造成著名的多線程同步問(wèn)題。在Java中,線程被封裝為Thread類,一種很常見(jiàn)的思維是維護(hù)一個(gè)共有的哈希表,然后將線程的私有變量存儲(chǔ)起來(lái),當(dāng)線程需要取用數(shù)據(jù)時(shí),從哈希表中讀取私有數(shù)據(jù)。
val threadData = ConcurrentHashMap<Thread,Any>()
但是如果這樣的話,程序員就需要自己在程序中維護(hù)這么一個(gè)表,且不說(shuō)自己能不能記住到底存儲(chǔ)了什么,光是表的維護(hù)就已經(jīng)很麻煩了,而且表的更新特別耗費(fèi)資源。因此,比較好的方法是將數(shù)據(jù)存儲(chǔ)封裝在Thread這個(gè)類的內(nèi)部,由于每一個(gè)線程都是一個(gè)Thread對(duì)象,它們需要這個(gè)值的時(shí)候直接讀取其內(nèi)部的值即可。Android采用了ThreadLocal這個(gè)類,實(shí)現(xiàn)了在指定線程中存儲(chǔ)數(shù)據(jù)的功能。
首先我們來(lái)看一下ThreadLocal類怎么使用:
fun test()
{
val localAge = ThreadLocal<Int>() // 申請(qǐng)一個(gè)Int類型的線程私有變量
localAge.set(10) //主線程中修改值
Log.d(Thread.currentThread().toString(),localAge.get().toString()+"-> $localAge")
Thread({
localAge.set(12345) //其他線程中修改值
Log.d(Thread.currentThread().toString(),localAge.get().toString()+"-> $localAge")
}, "Thread#2").start()
Thread({
localAge.set(18) //其他線程中修改值
Log.d(Thread.currentThread().toString(),localAge.get().toString()+"-> $localAge")
},"Thread#3").start()
localAge.set(-33) //主線程中第二次修改值
Log.d(Thread.currentThread().toString(),localAge.get().toString()+"-> $localAge")
}
輸出結(jié)果:
01-09 21:33:50.274 11935-11935/cn.suiseiseki.www.androidstudy D/Thread[main,5,main]: 10-> java.lang.ThreadLocal@4368aaa8
01-09 21:33:50.274 11935-11950/cn.suiseiseki.www.androidstudy D/Thread[Thread#2,5,main]: 12345-> java.lang.ThreadLocal@4368aaa8
01-09 21:33:50.279 11935-11935/cn.suiseiseki.www.androidstudy D/Thread[main,5,main]: -33-> java.lang.ThreadLocal@4368aaa8
01-09 21:33:50.279 11935-11951/cn.suiseiseki.www.androidstudy D/Thread[Thread#3,5,main]: 18-> java.lang.ThreadLocal@4368aaa8
可以看到,在不同線程中訪問(wèn)同一個(gè)Thread對(duì)象,其獲取的值不一樣,這就是ThreadLocal的奇妙之處
下面我們分析一下ThreadLocal的主要方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
其實(shí)代碼已經(jīng)很明顯了,就是通過(guò)不同的Thread對(duì)象獲取其Thread內(nèi)部維護(hù)的ThreadLocalMap,然后查詢是否有這個(gè)參數(shù),如果有就返回這個(gè)參數(shù)的值,如果沒(méi)有則返回null.對(duì)于set方法來(lái)說(shuō),首先查找線程是否創(chuàng)建了ThreadLocalMap,如果沒(méi)有則創(chuàng)建之,然后就是正常的map操作了
如果你感興趣的話,可以再深入了解一下ThreadLocalMap這個(gè)類,它內(nèi)部采用弱引用維護(hù)了一個(gè)哈希表,這里不再詳細(xì)介紹。
總之,現(xiàn)在我們已經(jīng)知道,多個(gè)線程之間確實(shí)可以互不干擾地存儲(chǔ)和修改某些數(shù)據(jù),這是線程間通信的基礎(chǔ)。
MessageQueue:消息隊(duì)列
Android中的線程間通信涉及到四個(gè)組件:Handler,Looper,Message,MessageQueue
MessageQueue稱為消息隊(duì)列,得力于上文所述的ThreadLocal類,現(xiàn)在每一個(gè)線程可以擁有自己的MessageQueue了。MessageQueue通過(guò)一個(gè)單鏈表來(lái)維護(hù)一個(gè)消息隊(duì)列,因?yàn)閱捂湵碓诓迦牒蛣h除過(guò)程中有性能優(yōu)勢(shì)。作為一個(gè)隊(duì)列,它自然有兩個(gè)方法,用于入隊(duì)(插隊(duì))的enqueueMessage方法和出隊(duì)的next方法
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
可以看到,enqueueMessage方法先檢查Message的合法性,然后再根據(jù)Message的when屬性將Message插入單鏈表中。
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
next方法是一個(gè)無(wú)限循環(huán)的方法,如果MessageQueue隊(duì)列中沒(méi)有消息,此方法會(huì)堵塞,直到新的消息到來(lái)位置,它會(huì)將消息取出,然后再鏈表中刪除它。
Looper:MessageQueue的使用者
有了消息隊(duì)列之后,我們還需要一個(gè)使用消息隊(duì)列的對(duì)象:Looper。從Looper的名字可以看出,它代表某種循環(huán)的意思。為了讓某個(gè)線程能不斷地處理消息(Message),我們就需要為線程創(chuàng)建Looper:
Thread(
{
Looper.prepare()
val handler = Handler()
Looper.loop()
}
).start()
注意這里需要調(diào)用Looper的靜態(tài)方法。當(dāng)然,我們也可以隨時(shí)停止Looper,在線程中調(diào)用quit或者quitSatety即可:
Looper.myLooper().quit()
Looper.myLooper().quitSafely()
可以看到,Looper的主要方法都是靜態(tài)方法,便于我們?cè)诰€程的任意位置調(diào)用它們
Looper內(nèi)部最重要的方法就是loop()方法,它是一個(gè)死循環(huán),跳出循環(huán)的唯一方式是其MessageQueue的next返回了null(正常情況下應(yīng)該是被堵塞的)。當(dāng)Looper調(diào)用quit或者quitSafely時(shí),它會(huì)通知消息隊(duì)列退出,使其返回null值。
如果MessageQueue的next返回了Message消息,它會(huì)將這個(gè)消息交給Handler去處理。
Handler:消息的發(fā)送與處理者
Handler的工作就是發(fā)送消息和處理消息,它主要通過(guò)兩個(gè)方法:sendMessage和post(runnable)去發(fā)送消息
它們的底層過(guò)程就是向消息隊(duì)列中插入一條消息,通過(guò)Looper接收后又交給了它自己的dispatchMessage方法去處理:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先,它會(huì)檢查消息本身的callback是否為空(這適用于post方法發(fā)送的runnable對(duì)象消息),如果不為空則由這個(gè)runnable對(duì)象接管執(zhí)行。
如果為空,再檢查本Handler自身的mCallback是否為空,如果不為空則由這個(gè)對(duì)象代為處理。
如果mCallback也為空,則在自身的handleMessage方法中執(zhí)行了,這是常規(guī)操作,自己在Handler的子類中實(shí)現(xiàn)這個(gè)方法即可
Handler還有一個(gè)很方便的構(gòu)造方法,傳入一個(gè)指定Looper來(lái)構(gòu)造Looper:
public Handler(Looper looper) {
this(looper, null, false);
}
比如 val handler = Handler(Looper.getMainLooper()),快速在其他線程中獲取主線程的handler
作者:黑心石
鏈接:http://www.lxweimin.com/p/8cb3c4f8978a
來(lái)源:簡(jiǎn)書(shū)
簡(jiǎn)書(shū)著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。