不同角度看Handler——另類三問


之前有一章節介紹了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故意設計的精妙之處了,他可以在不同的線程進行讀寫數據而且線程之間互不干擾。

畫個圖方便理解記憶:

ThreadLocal.PNG

當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啟動流程中涉及到了哪些功能?等等。有機會再和大家聊聊~

有一起學習的小伙伴可以關注下❤️我的公眾號——碼上積木,每天剖析一個知識點,我們一起積累知識。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM