Android怎樣保證一個線程最多僅僅能有一個Looper?

來源:互聯網
上載者:User

標籤:comm   src   cal   existing   內部類   變數   comment   product   init   

1. 怎樣建立Looper?

Looper的構造方法為private,所以不能直接使用其構造方法建立。

private Looper(boolean quitAllowed) {    mQueue = new MessageQueue(quitAllowed);    mThread = Thread.currentThread();}

要想在當前線程建立Looper。需使用Looper的prepare方法,Looper.prepare()。
假設如今要我們來實現Looper.prepare()這種方法,我們該怎麼做?我們知道,Android中一個線程最多僅僅能有一個Looper,若在已有Looper的線程中調用Looper.prepare()會拋出RuntimeException(“Only one Looper may be created per thread”)。

面對這種需求,我們可能會考慮使用一個HashMap,當中Key為線程ID,Value為與線程關聯的Looper,再加上一些同步機制,實現Looper.prepare()這種方法,代碼例如以下:

public class Looper {    static final HashMap<Long, Looper> looperRegistry = new HashMap<Long, Looper>();    private static void prepare() {        synchronized(Looper.class) {            long currentThreadId = Thread.currentThread().getId();            Looper l = looperRegistry.get(currentThreadId);            if (l != null)                throw new RuntimeException("Only one Looper may be created per thread");            looperRegistry.put(currentThreadId, new Looper(true));        }    }    ...}

上述方法對Looper.class對象進行了加鎖。這些加鎖開銷有可能造成效能瓶頸。
有沒有更好的方法實現Looper.prepare()方法?看一看Android的中Looper的原始碼。

public class Looper {    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();    public static void prepare() {       prepare(true);    }    private static void prepare(boolean quitAllowed) {       if (sThreadLocal.get() != null) {           throw new RuntimeException("Only one Looper may be created per thread");       }       sThreadLocal.set(new Looper(quitAllowed));    }    ...}

prepare()方法中調用了ThreadLocal的get和set方法。然而整個過程沒有加入同步鎖,Looper是怎樣實現安全執行緒的?

2. ThreadLocal

ThreadLocal位於java.lang包中,下面是JDK文檔中對該類的描寫敘述

Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values.

大致意思是,ThreadLocal實現了執行緒區域儲存。

全部線程共用同一個ThreadLocal對象,但不同線程僅能訪問與其線程相關聯的值。一個線程改動ThreadLocal對象對其它線程沒有影響。

ThreadLocal為編寫多線程並發程式提供了一個新的思路。例如以所看到的,我們能夠將ThreadLocal理解為一Block Storage區,將這一大Block Storage區切割為多塊小的儲存區。每一個線程擁有一塊屬於自己的儲存區,那麼對自己的儲存區操作就不會影響其它線程。對於ThreadLocal<Looper>,則每一小Block Storage區中就儲存了與特定線程關聯的Looper。


3. ThreadLocal的內部實現原理3.1 Thread、ThreadLocal和Values的關係

Thread的成員變數localValues代表了線程特定變數,類型為ThreadLocal.Values。由於線程特定變數可能會有多個,而且類型不確定,所以ThreadLocal.Values有一個table成員變數,類型為Object數組。這個localValues能夠理解為二維儲存區中與特定線程相關的一列。
ThreadLocal類則相當於一個代理。真正操作線程特定儲存區table的是其內部類Values。

3.2 set方法
public void set(T value) {    Thread currentThread = Thread.currentThread();    Values values = values(currentThread);    if (values == null) {        values = initializeValues(currentThread);    }    values.put(this, value);}Values values(Thread current) {    return current.localValues;}

既然與特定線程相關,所以先擷取當前線程,然後擷取當前線程特定儲存,即Thread中的localValues,若localValues為空白。則建立一個,最後將value存入values中。

void put(ThreadLocal<?> key, Object value) {    cleanUp();    // Keep track of first tombstone. That‘s where we want to go back    // and add an entry if necessary.    int firstTombstone = -1;    for (int index = key.hash & mask;; index = next(index)) {        Object k = table[index];        if (k == key.reference) {            // Replace existing entry.            table[index + 1] = value;            return;        }        if (k == null) {            if (firstTombstone == -1) {                // Fill in null slot.                table[index] = key.reference;                table[index + 1] = value;                size++;                return;            }            // Go back and replace first tombstone.            table[firstTombstone] = key.reference;            table[firstTombstone + 1] = value;            tombstones--;            size++;            return;        }        // Remember first tombstone.        if (firstTombstone == -1 && k == TOMBSTONE) {            firstTombstone = index;        }    }}

從put方法中,ThreadLocal的reference和值都會存進table,索引分別為index和index+1。
對於Looper這個範例,
table[index] = sThreadLocal.reference;(指向自己的一個弱引用)
table[index + 1] = 與當前線程關聯的Looper。

3.3 get方法
public T get() {    // Optimized for the fast path.    Thread currentThread = Thread.currentThread();    Values values = values(currentThread);    if (values != null) {        Object[] table = values.table;        int index = hash & values.mask;        if (this.reference == table[index]) {            return (T) table[index + 1];        }    } else {        values = initializeValues(currentThread);    }    return (T) values.getAfterMiss(this);}

首先取出與線程相關的Values,然後在table中尋找ThreadLocal的reference對象在table中的位置。然後返回下一個位置所儲存的對象。即ThreadLocal的值,在Looper這個範例中就是與當前線程關聯的Looper對象。

從set和get方法能夠看出,其所操作的都是當前線程的localValues中的table數組。所以不同線程調用同一個ThreadLocal對象的set和get方法互不影響,這就是ThreadLocal為解決多線程程式的並發問題提供了一種新的思路。

4. ThreadLocal背後的設計思想Thread-Specific Storage模式

Thread-Specific Storage讓多個線程能夠使用同樣的”邏輯全域“訪問點來擷取執行緒區域的對象。避免了每次訪問對象的鎖定開銷。

4.1 Thread-Specific Storage模式的起源

errno機制被廣泛用於一些作業系統平台。

errno 是記錄系統的最後一次錯誤碼。對於單線程程式。在全域範圍內實現errno的效果不錯,但在多線程作業系統中,多線程並發可能導致一個線程設定的errno值被其它線程錯誤解讀。

當時非常多遺留庫和應用程式都是基於單線程編寫,為了在不改動既有介面和遺留代碼的情況下。解決多線程訪問errno的問題,Thread-Specific Storage模式誕生。

4.2 Thread-Specific Storage模式的整體結構

線程特定對象,相當於Looper。
線程特定對象集包括一組與特定線程相關聯的線程特定對象。

每一個線程都有自己的線程特定對象集。

相當於ThreadLocal.Values。

線程特定對象集能夠儲存線上程內部或外部。Win32、Pthread和Java都對線程特定資料有支援,這種情況下線程特定對象集能夠儲存線上程內部。
線程特定對象代理,讓client能夠像訪問常規對象一樣訪問線程特定對象。假設沒有代理,client必須直接訪問線程特定對象集並顯示地使用鍵。

相當於ThreadLocal<Looper>。

從概念上講。可將Thread-Specific Storage的結構視為一個二維矩陣,每一個鍵相應一行。每一個線程相應一列。第k行、第t列的矩陣元素為指向相應線程特定對象的指標。線程特定對象代理和線程特定對象集協作,嚮應用程式線程提供一種訪問第k行、第t列對象的安全機制。

注意。這個模型僅僅是類比。實際上Thread-Specific Storage模式的實現並非使用二維矩陣,由於鍵不一定是相鄰整數。

參考資料
  1. Thread-local storage
  2. 面向模式的軟體架構·卷2:並發和連網對象模式

Android怎樣保證一個線程最多僅僅能有一個Looper?

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.