標籤:
前言:
最近在學習總結Android的動畫效果,當學到Android屬性動畫的時候大致看了下原始碼,裡面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及興趣!查閱了一下資料發現Android最重要的Handler訊息機制裡面的Looper儲存也是採用ThreadLocal,開源架構EventBus儲存當前線程下的發送事件隊列狀態也是採用ThreadLocal,那麼為何要使用ThreadLocal呢?ThreadLocal是什麼呢?它能解決什麼樣的問題呢?帶著這麼疑問來學習下ThreadLocal。
ThreadLocal介紹
ThreadLocal如果單純從字面上理解的話好像是“本地線程”的意思,其實並不是這個意思,只是這個名字起的太容易讓人誤解了,它的真正的意思是執行緒區域變數。看看官方怎麼說的。
/** * Implements a thread-local storage, that is, a variable for which each thread * has its own value. All threads share the same {@code 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 * {@code null} values. * * @see java.lang.Thread * @author Bob Lee */
哈哈作為學渣英語不好,藉助百度翻譯了一下。翻譯如下也不知道對不對
實現一個執行緒區域的儲存,也就是說,每個線程都有自己的局部變數。所有線程都共用一個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瞭解不是很深刻,僅僅理解那麼一點點,也許有些觀點不一定正確,希望看到的朋友批評指正謝謝。如果大家想通過一個例子來學習的話,個人建議看下EventBus中使用ThreadLocal範例,用的很巧妙又很容易讓人理解,只是ThreadLocal本身我們在日常項目開發中使用的比較少,一會半會的很難找到合適的情境來搞懂它。
Android線程管理之ThreadLocal理解及應用情境(五)