天天看點

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

故意設計的精妙之處了,他可以在不同的線程進行讀寫資料而且線程之間互不幹擾。

畫個圖友善了解記憶:

Handler另類難點三問

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