天天看點

ThreadLocal應用場景以及源碼分析

一、應用篇

ThreadLocal介紹

   ThreadLocal如果單純從字面上了解的話好像是“本地線程”的意思,其實并不是這個意思,隻是這個名字起的太容易讓人誤解了,它的真正的意思是線程本地變量。

   實作一個線程本地的存儲,也就是說,每個線程都有自己的局部變量。所有線程都共享一個ThreadLocal對象,但是每個線程在通路這些變量的時候能得到不同的值,每個線程可以更改這些變量并且不會影響其他的線程,并且支援null值。

ThreadLocal了解

我們先看下屬性動畫為每個線程設定AnimationHeadler的

private static AnimationHandler getOrCreateAnimationHandler() {
        AnimationHandler handler = sAnimationHandler.get();
        if (handler == null) {
            handler = new AnimationHandler();
            sAnimationHandler.set(handler);
        }
        return handler;
    }      

因為protected static ThreadLocal<AnimationHandler> sAnimationHandler =new ThreadLocal<AnimationHandler>();這裡沒有采用初始化值,這裡不是通過一個變量的拷貝而是每個線程通過new建立一個對象出來然後儲存。很多人認為ThreadLocal是為了解決共享對象的多線程通路問題的,這是錯誤的說法,因為無論是通過初始化變量的拷貝還是直接通過new建立自己局部變量,ThreadLocal.set()  到線程中的對象是該線程自己使用的對象,其他線程是不需要通路的,也通路不到的。各個線程中通路的是不同的對象,改變的也是自己獨立的對象,本身就不屬于同一個對象,沒有共享的概念,更加不可能是解決共享對象的多線程通路的。

通過上面的了解總結以下幾點

 1.每個線程讀擁有自己的局部變量

     每個線程都有一個獨立于其他線程的上下文來儲存這個變量,一個線程的本地變量對其他線程是不可見的

 2.獨立于變量的初始化副本,或者初始化一個屬于自己的變量

     ThreadLocal可以給一個初始值,而每個線程都會獲得這個初始化值的一個副本,這樣才能保證不同的線程都有一份拷貝,同樣也可以new的方式為線程建立一個變量

 3.變量改變隻與目前線程關聯,線程之間互不幹擾

    ThreadLocal 不是用于解決共享變量的問題的,不是為了協調線程同步而存在,而是為了友善每個線程處理自己的狀态而引入的一個機制。

是以ThreadLocal既不是為了解決共享多線程的通路問題,更不是為了解決線程同步問題,ThreadLocal的設計初衷就是為了提供線程内部的局部變量,友善在本線程内随時随地的讀取,并且與其他線程隔離。

ThreadLocal使用場景

    說了那麼多的概念,歸根到底我們在什麼時候才使用ThreadLocal呢?很多時候我們會建立一些靜态域來儲存全局對象,那麼這個對象就可能被任意線程通路,如果能保證是線程安全的,那倒是沒啥問題,但是有時候很難保證線程安全,這時候我們就需要為每個線程都建立一個對象的副本,我們也可以用ConcurrentMap<Thread, Object>來儲存這些對象,這樣會比較麻煩,比如當一個線程結束的時候我們如何删除這個線程的對象副本呢?如果使用ThreadLocal就不用有這個擔心了,ThreadLocal保證每個線程都保持對其線程局部變量副本的隐式引用,隻要線程是活動的并且  ThreadLocal 執行個體是可通路的;線上程消失之後,其線程局部執行個體的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。經查閱資料大緻得到以下兩種場景:

1.)當某些資料以線程為作用域,并且不同線程擁有不同資料副本的時候。

   ThreadLocal使用場合主要解決多線程中資料因并發産生不一緻的問題。ThreadLocal以空間換時間,為每個線程的中并發通路的資料提供一個副本,通過通路副本來運作業務,這樣的結果是耗費了記憶體,但大大減少了線程同步所帶來的線程消耗,也減少了線程并發控制的複雜度。

   例如Android的Handler消息機制,對于Handler來說,它需要擷取目前線程的looper很顯然Looper的作用域就是線程并且不同線程具有不同的Looper,這個時候通過ThreadLocal就可以輕松實作Looper線上程中的存取。再例如開源架構EventBus,EventBus需要擷取目前線程的PostingThreadState對象,不同的PostingThreadState同樣作用于不同的線程,EventBus可以很輕松的擷取目前線程下的PostingThreadState對象,然後進行相關操作。

2.)複雜邏輯下對象傳遞,比如監聽器的傳遞

   使用參數傳遞的話:當函數調用棧更深時,設計會很糟糕,為每一個線程定義一個靜态變量監聽器,如果是多線程的話,一個線程就需要定義一個靜态變量,無法擴充,這時候使用ThreadLocal就可以解決問題。

 ThreadLocal使用舉例

  舉一個簡單的例子,讓每個線程擁有自己唯一的一個任務隊列,類似EventBus的實作。

private static final ThreadLocal<PriorityQueue<TaskItem>> queueThreadLocal = new ThreadLocal<PriorityQueue<TaskItem>>() {
        @Override
        protected PriorityQueue<TaskItem> initialValue() {
            return new PriorityQueue<>(5);
        }
    };


    public PriorityQueue<TaskItem> getTaskQueue() {
        PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
        return taskItems;
    }


    public void addTask(TaskItem taskItem) {
        PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
        taskItems.add(taskItem);
    }

    public void removeTask(TaskItem taskItem) {
        PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
        if (taskItems.contains(taskItem)) {
            taskItems.remove(taskItem);
        }
    }

    private void exceTask() {
        PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
        if (!taskItems.isEmpty()) {
            TaskItem taskItem = taskItems.poll();
            taskItem.exceTask();
        }
    }      

 附上TaskItme代碼:

public class TaskItem implements Comparable {
    private long Id;
    private String name;
    private int priority;

    public long getId() {
        return Id;
    }

    public void setId(long id) {
        Id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }

    @Override
    public int compareTo(Object arg0) {
        if (TaskItem.class.isInstance(arg0)) {
            TaskItem tm = (TaskItem) arg0;
            if (tm.priority > priority) {
                return -1;
            } else if (tm.priority < priority) {
                return 1;
            }
        }
        return 0;
    }

    public void exceTask() {
        Log.e("exceTask", "exceTask---id:" + Id + " name:" + name);
    }

}      

經過上面代碼可以看到,你是在哪個線程送出的任務自然而然的就添加到線程所屬的任務隊列裡面,這裡其實通過ConcurrentMap<Thread, Object>儲存也是可以的,上面也說了相對比較麻煩。

二、源碼篇

       ThreadLocal是一個線程内部的資料存儲類,通過它可以在指定的線程中存儲資料,資料存儲以後,隻有在指定線程中可以擷取到存儲的資料,對于其它線程來說無法擷取到資料。在日常開發中用到ThreadLocal的地方較少,但是在某些特殊的場景下,通過ThreadLocal可以輕松地實作一些看起來很複雜的功能,這一點在Android的源碼中也有所展現,比如Looper、ActivityThread以及AMS中都用到了ThreadLocal。具體到ThreadLocal的使用場景,這個不好統一地來描述,一般來說,當某些資料是以線程為作用域并且不同線程具有不同的資料副本的時候,就可以考慮采用ThreadLocal。比如對于Handler來說,它需要擷取目前線程的Looper,很顯然Looper的作用域就是線程并且不同線程具有不同的Looper,這個時候通過ThreadLocal就可以輕松實作Looper線上程中的存取,如果不采用ThreadLocal,那麼系統就必須提供一個全局的哈希表供Handler查找指定線程的Looper,這樣一來就必須提供一個類似于LooperManager的類了,但是系統并沒有這麼做而是選擇了ThreadLocal,這就是ThreadLocal的好處。

       ThreadLocal另一個使用場景是複雜邏輯下的對象傳遞,比如監聽器的傳遞,有些時候一個線程中的任務過于複雜,這可能表現為函數調用棧比較深以及代碼入口的多樣性,在這種情況下,我們又需要監聽器能夠貫穿整個線程的執行過程,這個時候可以怎麼做呢?其實就可以采用ThreadLocal,采用ThreadLocal可以讓監聽器作為線程内的全局對象而存在,線上程内部隻要通過get方法就可以擷取到監聽器。而如果不采用ThreadLocal,那麼我們能想到的可能是如下兩種方法:第一種方法是将監聽器通過參數的形式在函數調用棧中進行傳遞,第二種方法就是将監聽器作為靜态變量供線程通路。上述這兩種方法都是有局限性的。第一種方法的問題時當函數調用棧很深的時候,通過函數參數來傳遞監聽器對象這幾乎是不可接受的,這會讓程式的設計看起來很糟糕。第二種方法是可以接受的,但是這種狀态是不具有可擴充性的,比如如果同時有兩個線程在執行,那麼就需要提供兩個靜态的監聽器對象,如果有10個線程在并發執行呢?提供10個靜态的監聽器對象?這顯然是不可思議的,而采用ThreadLocal每個監聽器對象都在自己的線程内部存儲,根據就不會有方法2的這種問題。

       介紹了那麼多ThreadLocal的知識,可能還是有點抽象,下面通過實際的例子為大家示範ThreadLocal的真正含義。首先定義一個ThreadLocal對象,這裡選擇Boolean類型的,如下所示:

private ThreadLocal<Boolean>mBooleanThreadLocal = new ThreadLocal<Boolean>();

       然後分别在主線程、子線程1和子線程2中設定和通路它的值,代碼如下所示:

[java] view plain copy

  1. mBooleanThreadLocal.set(true);  
  2. Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());  
  3. new Thread("Thread#1") {  
  4.     @Override  
  5.     public void run() {  
  6.         mBooleanThreadLocal.set(false);  
  7.         Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());  
  8.     };  
  9. }.start();  
  10. new Thread("Thread#2") {  
  11.         Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());  

       在上面的代碼中,在主線程中設定mBooleanThreadLocal的值為true,在子線程1中設定mBooleanThreadLocal的值為false,在子線程2中不設定mBooleanThreadLocal的值,然後分别在3個線程中通過get方法去mBooleanThreadLocal的值,根據前面對ThreadLocal的描述,這個時候,主線程中應該是true,子線程1中應該是false,而子線程2中由于沒有設定值,是以應該是null,安裝并運作程式,日志如下所示:

D/TestActivity(8676):[Thread#main]mBooleanThreadLocal=true

D/TestActivity(8676):[Thread#1]mBooleanThreadLocal=false

D/TestActivity(8676):[Thread#2]mBooleanThreadLocal=null

       從上面日志可以看出,雖然在不同線程中通路的是同一個ThreadLocal對象,但是它們通過ThreadLocal來擷取到的值卻是不一樣的,這就是ThreadLocal的奇妙之處。結合這這個例子然後再看一遍前面對ThreadLocal的兩個使用場景的理論分析,大家應該就能比較好地了解ThreadLocal的使用方法了。ThreadLocal之是以有這麼奇妙的效果,是因為不同線程通路同一個ThreadLocal的get方法,ThreadLocal内部會從各自的線程中取出一個數組,然後再從數組中根據目前ThreadLocal的索引去查找出對應的value值,很顯然,不同線程中的數組是不同的,這就是為什麼通過ThreadLocal可以在不同的線程中維護一套資料的副本并且彼此互不幹擾。

       對ThreadLocal的使用方法和工作過程做了一個介紹後,下面分析下ThreadLocal的内部實作, ThreadLocal是一個泛型類,它的定義為public class ThreadLocal<T>,隻要弄清楚ThreadLocal的get和set方法就可以明白它的工作原理。

  2.1  set方法

       首先看ThreadLocal的set方法,如下所示:

public void set(T var1) { Thread var2 = Thread.currentThread(); ThreadLocal.ThreadLocalMap var3 = this.getMap(var2); if(var3 != null) { var3.set(this, var1); } else { this.createMap(var2, var1); }

}

首先會擷取目前線程,然後通過getMap建立map集合。如下所示:

[java] view plain copy print?

  1. <span style="font-size: 14px;">ThreadLocal.ThreadLocalMap getMap(Thread var1) {  
  2.     return var1.threadLocals;  
  3. }  
  4. void createMap(Thread var1, T var2) {  
  5.     var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);  
  6. }</span>  
ThreadLocal.ThreadLocalMap getMap(Thread var1) {
    return var1.threadLocals;
}

void createMap(Thread var1, T var2) {
    var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);
}      

getmap傳回的是ThreadLocal.ThreadLocalMap對象,我們接着往下看

[html] view plain copy print?

  1. <span style="font-size: 14px;">ThreadLocalMap(ThreadLocal<?> var1, Object var2) {  
  2.     this.size = 0;  
  3.     this.table = new ThreadLocal.ThreadLocalMap.Entry[16];  
  4.     int var3 = var1.threadLocalHashCode & 15;  
  5.     this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);  
  6.     this.size = 1;  
  7.     this.setThreshold(16);  
ThreadLocalMap(ThreadLocal<?> var1, Object var2) {
    this.size = 0;
    this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
    int var3 = var1.threadLocalHashCode & 15;
    this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
    this.size = 1;
    this.setThreshold(16);
}      

我們在來看看Entry

  1. <span style="font-size: 14px;">static class Entry extends WeakReference<ThreadLocal<?>> {  
  2.     Object value;  
  3.     Entry(ThreadLocal<?> var1, Object var2) {  
  4.         super(var1);  
  5.         this.value = var2;  
  6.     }  
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;

    Entry(ThreadLocal<?> var1, Object var2) {
        super(var1);
        this.value = var2;
    }
}      
通過弱引用Entry集合來存儲資料,這樣的好處待到下次記憶體回收時垃圾肯定被回收了。這時将var2的值已經存到了entry的value中。      
我們具體來看看var3.set方法裡面的代碼      
  1. <span style="font-size: 14px;">private void set(ThreadLocal<?> var1, Object var2) {  
  2.     ThreadLocal.ThreadLocalMap.Entry[] var3 = this.table;  
  3.     int var4 = var3.length;  
  4.     int var5 = var1.threadLocalHashCode & var4 - 1;  
  5.     for(ThreadLocal.ThreadLocalMap.Entry var6 = var3[var5]; var6 != null; var6 = var3[var5 = nextIndex(var5, var4)]) {  
  6.         ThreadLocal var7 = (ThreadLocal)var6.get();  
  7.         if(var7 == var1) {  
  8.             var6.value = var2;  
  9.             return;  
  10.         }  
  11.         if(var7 == null) {  
  12.             this.replaceStaleEntry(var1, var2, var5);  
  13.     var3[var5] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);  
  14.     int var8 = ++this.size;  
  15.     if(!this.cleanSomeSlots(var5, var8) && var8 >= this.threshold) {  
  16.         this.rehash();  
private void set(ThreadLocal<?> var1, Object var2) {
    ThreadLocal.ThreadLocalMap.Entry[] var3 = this.table;
    int var4 = var3.length;
    int var5 = var1.threadLocalHashCode & var4 - 1;

    for(ThreadLocal.ThreadLocalMap.Entry var6 = var3[var5]; var6 != null; var6 = var3[var5 = nextIndex(var5, var4)]) {
        ThreadLocal var7 = (ThreadLocal)var6.get();
        if(var7 == var1) {
            var6.value = var2;
            return;
        }

        if(var7 == null) {
            this.replaceStaleEntry(var1, var2, var5);
            return;
        }
    }

    var3[var5] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
    int var8 = ++this.size;
    if(!this.cleanSomeSlots(var5, var8) && var8 >= this.threshold) {
        this.rehash();
    }

}      
通過建立table數組來存儲弱引用entry集合。      
忽略其中算法,最終會把var2值放入到Entry集合中。上面的是在Entry集合中存儲資料,下面是從集合中取資料      

2.2 get方法

  1. <span style="font-size: 14px;">public T get() {  
  2.     Thread var1 = Thread.currentThread();  
  3.     ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);  
  4.     if(var2 != null) {  
  5.         ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);  
  6.         if(var3 != null) {  
  7.             Object var4 = var3.value;  
  8.             return var4;  
  9.     return this.setInitialValue();  
public T get() {
    Thread var1 = Thread.currentThread();
    ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
    if(var2 != null) {
        ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
        if(var3 != null) {
            Object var4 = var3.value;
            return var4;
        }
    }

    return this.setInitialValue();
}      
首先是getMap方法,還是傳回ThreadLocal.ThreadLocalMap對象,重點我們來看      
getEntry方法      
  1. <span style="font-size: 14px;">private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> var1) {  
  2.     int var2 = var1.threadLocalHashCode & this.table.length - 1;  
  3.     ThreadLocal.ThreadLocalMap.Entry var3 = this.table[var2];  
  4.     return var3 != null && var3.get() == var1?var3:this.getEntryAfterMiss(var1, var2, var3);  
private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> var1) {
    int var2 = var1.threadLocalHashCode & this.table.length - 1;
    ThreadLocal.ThreadLocalMap.Entry var3 = this.table[var2];
    return var3 != null && var3.get() == var1?var3:this.getEntryAfterMiss(var1, var2, var3);
}      
通過索引varl查找出變量var3(通過table數組),接着傳回我們想要索引的Entry對象。      
上一篇: 阿裡面經