1. 程式人生 > >Android訊息機制(一)之 ThreadLocal

Android訊息機制(一)之 ThreadLocal

前言

  今天重溫了Android開發藝術探索上的訊息機制,花了一些時間,書上寫很好,但是可能文章一些先後順序問題,不是特別好理解,這篇文章博主用了自己的理解,看原始碼,結合書上的知識,希望大家能更容易理解。(可能會寫的不太好。。。不正確的地方歡迎指出)
  Android的訊息機制主要是指Handler的執行機制。Handler的執行需要底層的MessageQueue和Looper的支撐。MessageQueue是訊息佇列,用於儲存一組訊息(雖然叫訊息佇列,其實是用單鏈表的資料結構來儲存訊息列表)。Looper中文翻譯為迴圈,這裡可以理解為訊息迴圈。它會無限迴圈訪問訊息佇列中有沒有新的訊息需要處理。
  Looper中有個特殊的概念,就是ThreadLocal,這篇文章主要簡要講解這個ThreadLocal是什麼東西,有什麼用。至於Handler的執行機制具體是怎樣的,後面會寫關於這方面的文章。
  看完本文或者已經瞭解ThreadLocal的知識的,可以瞭解一下 MessageQueue 和 Looper 的工作原理 ,加深對訊息機制的瞭解   

Android訊息機制(二)之 MessageQueue 與 Looper
   
   

Thread中存放資料

  每條執行緒都會儲存關於那條執行緒的資料,或者說是跟執行緒有關的資訊。它們都存在例項化後的執行緒物件中。只是有些資訊是這個物件必須有的,所以它們會被定義為成員變數。而有些資料可能在執行之後,程式設計師突然想放進去的,那麼成員變數就不能滿足了。所以Thread類有一個成員變數ThreadLocal.ThreadLocalMap 就專門用來儲存這類的資料了。它是ThreadLocal類中的一個內部類,這個內部類有一個成員變數table[],用於存放資料,這個內部類提供了儲存資料,獲取資料的方法。
  
下面是Thread類中的成員變數
這裡寫圖片描述

這裡寫圖片描述
  

所以ThreadLocal到底是什麼

  知道了上面一些前提之後,我們再來看看ThreadLocal在書上的說法:
  ThreadLocal是一個執行緒內部的資料儲存類,通過它可以在指定的執行緒中儲存資料,資料儲存以後,只有在指定執行緒中可以獲取到儲存的資料,對於其他執行緒來說則無法獲取到資料。
  現在就比較好理解了,ThreadLocal其實並不是用來真正存放資料的,資料還是放線上程物件中的。我的理解ThreadLocal就像一個工具類,可以通過他的提供的方法找到執行程式碼所在的執行緒中自己想要的資料。

舉個例子:

//首先定義一個ThreadLocal物件,指定儲存Boolean型別資料
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>(); //然後分別在主執行緒、子執行緒1、子執行緒2中設定和訪問它的值 mThreadLocal.set(true); Log.d(TAG,"[main]mThreadLocal"+mThreadLocal.get()); new Thread("Thread#1"){ @Override public void run() { mThreadLocal.set(false); Log.d(TAG,"[#1]mThreadLocal"+mThreadLocal.get()); } }.start(); new Thread("Thread#2"){ @Override public void run() { Log.d(TAG,"[#2]mThreadLocal"+mThreadLocal.get()); } }.start();

我們在主執行緒中設了true,線上程1中設了false,線上程2中沒有設值。
那麼打印出來的結果,就像預料的一樣,是true,false,null

所以,上面的結果我們看到的是,雖然在不同執行緒中訪問的是同一個ThreadLocal物件,但是它們ThreadLocal獲取的值卻是不一樣的,這就是ThreadLocal的功能。實質上就是ThreadLocal通過執行這段程式碼所在的執行緒、和這個ThreadLocal物件作為根據,去找所對應的Thread中ThreadLocal.ThreadLocalMap中的table[]找到了我們想要的值。
(利用所線上程作為根據可以理解,可是為什麼需要用到ThreadLocal的物件呢?)
  
  

ThreadLocal 中的方法

首先看ThreadLocal中的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,裡面又呼叫了ThreadLocalMap 的set方法去存放這個值,用了兩個引數,一個this,代表的是當前這個ThreadLocal物件,另一個則是要存放的值。那麼這個ThreadLocal物件有什麼用呢?我們再看看ThreadLocalMap的set方法。

 private void set(ThreadLocal key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

  裡面的具體實現演算法就不做過多的研究了,簡單的說就是用這個物件key的一些數值比如雜湊碼之類的形成一個標識。存放在table[]中,然後資料存放在這個標識的索引的下一個索引。簡單說就是這個陣列的資料就是。 標識、值、標識、值…..。
  其實很容易理解,這樣我們就可以根據那個物件,去get()到我們真正想要的值了。下面是這個get()方法。

//ThreadLocal的get()
   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
//ThreadLocalMap 的getEntry
 private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

ThreadLocal的應用

前面說了,Looper有用到。那麼是怎麼用到的呢?我們可以用Looper.myLoop()方法獲取當前執行緒的Looper例項。這個例項就會存線上程之中,和執行緒繫結在一起。

public final class Looper{
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

   public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

}

sThreadLocal.get()會到當前執行緒的ThreadLocalMap中取出這個Looper例項。不同執行緒取出的例項都不同。
  
  
  

—-參考文獻《Android開發藝術探索》 任玉剛著