Java中的線程回顧
在操作系統的概念里,進程是具有一定獨立功能的程序、它是系統進行資源分配和調度的一個獨立單位。
在Java的虛擬機中,進程擁有自己獨立的虛擬地址空間(準確的說是方法區和堆內存)。
而線程則是CPU調度的基本單元,它沒有自己獨立的虛擬地址空間,它依附于進程的虛擬地址空間。在Java虛擬機中,線程擁有的不過是PC計數器和方法棧而已。
在Android系統中,進程之間的通信稱為IPC,它主要由Binder完成,線程之間由于共享地址空間,各線程都可以對對象進行操作,因此線程間的通信機制更為簡單,由輕便的Handler和Looper完成。
線程獨有數據
由于同一個進程的線程共享資源,因此,如何保證數據在某線程的私有性變為極為重要,忽略這點會造成著名的多線程同步問題。在Java中,線程被封裝為Thread類,一種很常見的思維是維護一個共有的哈希表,然后將線程的私有變量存儲起來,當線程需要取用數據時,從哈希表中讀取私有數據。
val threadData = ConcurrentHashMap<Thread,Any>()
但是如果這樣的話,程序員就需要自己在程序中維護這么一個表,且不說自己能不能記住到底存儲了什么,光是表的維護就已經很麻煩了,而且表的更新特別耗費資源。因此,比較好的方法是將數據存儲封裝在Thread這個類的內部,由于每一個線程都是一個Thread對象,它們需要這個值的時候直接讀取其內部的值即可。Android采用了ThreadLocal這個類,實現了在指定線程中存儲數據的功能。
首先我們來看一下ThreadLocal類怎么使用:
fun test()
{
val localAge = ThreadLocal<Int>() // 申請一個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")
}
輸出結果:
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
可以看到,在不同線程中訪問同一個Thread對象,其獲取的值不一樣,這就是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();
}
其實代碼已經很明顯了,就是通過不同的Thread對象獲取其Thread內部維護的ThreadLocalMap,然后查詢是否有這個參數,如果有就返回這個參數的值,如果沒有則返回null.對于set方法來說,首先查找線程是否創建了ThreadLocalMap,如果沒有則創建之,然后就是正常的map操作了
如果你感興趣的話,可以再深入了解一下ThreadLocalMap這個類,它內部采用弱引用維護了一個哈希表,這里不再詳細介紹。
總之,現在我們已經知道,多個線程之間確實可以互不干擾地存儲和修改某些數據,這是線程間通信的基礎。
MessageQueue:消息隊列
Android中的線程間通信涉及到四個組件:Handler,Looper,Message,MessageQueue
MessageQueue稱為消息隊列,得力于上文所述的ThreadLocal類,現在每一個線程可以擁有自己的MessageQueue了。MessageQueue通過一個單鏈表來維護一個消息隊列,因為單鏈表在插入和刪除過程中有性能優勢。作為一個隊列,它自然有兩個方法,用于入隊(插隊)的enqueueMessage方法和出隊的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的合法性,然后再根據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方法是一個無限循環的方法,如果MessageQueue隊列中沒有消息,此方法會堵塞,直到新的消息到來位置,它會將消息取出,然后再鏈表中刪除它。
Looper:MessageQueue的使用者
有了消息隊列之后,我們還需要一個使用消息隊列的對象:Looper。從Looper的名字可以看出,它代表某種循環的意思。為了讓某個線程能不斷地處理消息(Message),我們就需要為線程創建Looper:
Thread(
{
Looper.prepare()
val handler = Handler()
Looper.loop()
}
).start()
注意這里需要調用Looper的靜態方法。當然,我們也可以隨時停止Looper,在線程中調用quit或者quitSatety即可:
Looper.myLooper().quit()
Looper.myLooper().quitSafely()
可以看到,Looper的主要方法都是靜態方法,便于我們在線程的任意位置調用它們
Looper內部最重要的方法就是loop()方法,它是一個死循環,跳出循環的唯一方式是其MessageQueue的next返回了null(正常情況下應該是被堵塞的)。當Looper調用quit或者quitSafely時,它會通知消息隊列退出,使其返回null值。
如果MessageQueue的next返回了Message消息,它會將這個消息交給Handler去處理。
Handler:消息的發送與處理者
Handler的工作就是發送消息和處理消息,它主要通過兩個方法:sendMessage和post(runnable)去發送消息
它們的底層過程就是向消息隊列中插入一條消息,通過Looper接收后又交給了它自己的dispatchMessage方法去處理:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先,它會檢查消息本身的callback是否為空(這適用于post方法發送的runnable對象消息),如果不為空則由這個runnable對象接管執行。
如果為空,再檢查本Handler自身的mCallback是否為空,如果不為空則由這個對象代為處理。
如果mCallback也為空,則在自身的handleMessage方法中執行了,這是常規操作,自己在Handler的子類中實現這個方法即可
Handler還有一個很方便的構造方法,傳入一個指定Looper來構造Looper:
public Handler(Looper looper) {
this(looper, null, false);
}
比如 val handler = Handler(Looper.getMainLooper()),快速在其他線程中獲取主線程的handler