之前有一章節介紹了Handler的常見面試題,今天就來說說另類的,可能你沒關注的其他問題,一起看看吧。
系統為什麼提供Handler
- 這點大家應該都知道一些,就是為了切換線程,主要就是為了解決在子線程無法通路UI的問題。
那麼為什麼系統
不允許
在子線程中通路UI呢?
- 因為
的UI控件不是線程安全的,是以采用單線程模型來處理UI操作,通過Handler切換UI通路的線程即可。Android
那麼為什麼不給UI控件
加鎖
呢?
- 因為加鎖會讓
通路的邏輯變得複雜,而且會降低UI
通路的效率,阻塞線程執行。UI
Handler是怎麼擷取到目前線程的Looper的
- 大家應該都知道
是綁定到線程上的,他的作用域就是線程,而且不同線程具有不同的Looper
,也就是要從不同的線程取出線程中的Looper
對象,這裡用到的就是Looper
。ThreadLocal
假設我們不知道有這個類,如果要完成這樣一個需求,從不同的線程擷取線程中的
Looper
,是不是可以采用一個全局對象,比如
hashmap
,用來存儲線程和對應的
Looper
?是以需要一個管理
Looper
的類,但是,線程中并不止這一個要存儲和擷取的資料,還有可能有其他的需求,也是跟線程所綁定的。是以,我們的系統就設計出了
ThreadLocal
這種工具類。
ThreadLocal
的工作流程是這樣的:我們從不同的線程可以通路同一個
ThreadLocal
的get方法,然後
ThreadLocal
會從各自的線程中取出一個數組,然後再數組中通過
ThreadLocal
的索引找出對應的value值。具體邏輯呢,我們還是看看代碼,分别是
ThreadLocal
的get方法和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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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();
}
複制
首先看看set方法,擷取到目前線程,然後取出線程中的
threadLocals
變量,是一個
ThreadLocalMap
類,然後将目前的
ThreadLocal
作為key,要設定的值作為
value
存到這個map中。
get方法
就同理了,還是擷取到目前線程,然後取出線程中的
ThreadLocalMap
執行個體,然後從中取到目前
ThreadLocal
對應的值。
其實可以看到,操作的對象都是線程中的
ThreadLocalMap
執行個體,也就是讀寫操作都隻限制線上程内部,這也就是
ThreadLocal
故意設計的精妙之處了,他可以在不同的線程進行讀寫資料而且線程之間互不幹擾。
畫個圖友善了解記憶:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICMyYTMvw1dvwlMvwlM3VWaWV2Zh1Wa-cmbw5CazcWZzVWa3YDMvw1NxADO3QDNtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
ThreadLocal.PNG
當MessageQueue 沒有消息的時候,在幹什麼,會占用CPU資源嗎。
-
沒有消息時,便阻塞在 loop 的MessageQueue
方法這裡。具體就是會調用到nativePollOnce方法裡,最終調用到queue.next()
進行阻塞等待。epoll_wait()
這時,主線程會進行休眠狀态,也就不會消耗CPU資源。當下個消息到達的時候,就會通過pipe管道寫入資料然後喚醒主線程進行工作。
這裡涉及到阻塞和喚醒的機制叫做
epoll 機制
。
先說說檔案描述符和I/O多路複用:
“在Linux作業系統中,可以将一切都看作是檔案,而檔案描述符簡稱fd,當程式打開一個現有檔案或者建立一個新檔案時,核心向程序傳回一個檔案描述符,可以了解為一個索引值。
”
“I/O多路複用是一種機制,讓單個程序可以監視多個檔案描述符,一旦某個描述符就緒(一般是讀就緒或寫就緒),能夠通知程式進行相應的讀寫操作
”
是以
I/O
多路複用其實就是一種監聽讀寫的通知機制,而Linux提供的三種 IO 複用方式分别是:
select、poll 和 epoll
。而這其中
epoll
是性能最好的多路I/O就緒通知方法。
是以,這裡用到的
epoll
其實就是一種I/O多路複用方式,用來監控多個檔案描述符的I/O事件。通過
epoll_wait
方法等待I/O事件,如果目前沒有可用的事件則阻塞調用線程。
拜拜
今天就說這麼多了,感興趣的朋友也可以繼續深究下去,比如epoll為什麼是性能最好的I/O多路複用方法?Handler在App啟動流程中涉及到了哪些功能?等等。有機會再和大家聊聊~早安。