前言:
最近在學習總結Android的動畫效果,當學到Android屬性動畫的時候大致看了下源代碼,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及興趣!查閱了一下資料發現Android最重要的Handler消息機制里面的Looper存儲也是采用ThreadLocal,開源框架EventBus存儲當前線程下的發送事件隊列狀態也是采用ThreadLocal,那么為何要使用ThreadLocal呢?ThreadLocal是什么呢?它能解決什么樣的問題呢?帶着這么疑問來學習下ThreadLocal。
線程管理相關文章地址:
- Android線程管理之Thread使用總結
- Android線程管理之ExecutorService線程池
- Android線程管理之ThreadPoolExecutor自定義線程池
- Android線程管理之AsyncTask異步任務
- Android線程管理之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本身我們在日常項目開發中使用的比較少,一會半會的很難找到合適的場景來搞懂它。
