之前有一章節介紹了Handler的常見面試題,今天就來說說另類的,可能你沒關注的其他問題,一起看看吧。
系統為什么提供Handler
- 這點大家應該都知道一些,就是為了切換線程,主要就是為了解決在子線程無法訪問UI的問題。
那么為什么系統不允許
在子線程中訪問UI呢?
- 因為
Android
的UI控件不是線程安全的,所以采用單線程模型來處理UI操作,通過Handler切換UI訪問的線程即可。
那么為什么不給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
故意設計的精妙之處了,他可以在不同的線程進行讀寫數據而且線程之間互不干擾。
畫個圖方便理解記憶:
當MessageQueue 沒有消息的時候,在干什么,會占用CPU資源嗎。
MessageQueue
沒有消息時,便阻塞在 loop 的queue.next()
方法這里。具體就是會調用到nativePollOnce方法里,最終調用到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啟動流程中涉及到了哪些功能?等等。有機會再和大家聊聊~
有一起學習的小伙伴可以關注下❤️我的公眾號——碼上積木,每天剖析一個知識點,我們一起積累知識。