Android線程管理之ThreadLocal理解及應用場景

前言:

     最近在學習總結Android的動畫效果,當學到Android屬性動畫的時候大體看了下源代碼,裏面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及興趣!查閱了一下資料發現Android最重要的Handler消息機制裏面的Looper存儲也是採用ThreadLocal,開源框架EventBus存儲當前線程下的發送事件隊列狀態也是採用ThreadLocal,那麼爲什麼要使用ThreadLocal呢?ThreadLocal是什麼呢?它能解決什麼樣的問題呢?帶着這麼疑問來學習下ThreadLocal。html

     線程管理相關文章地址:java

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() 到線程中的對象是該線程本身使用的對象,其餘線程是不須要訪問的,也訪問不到的。各個線程中訪問的是不一樣的對象,改變的也是本身獨立的對象,自己就不屬於同一個對象,沒有共享的概念,更加不多是解決共享對象的多線程訪問的。異步

經過上面的理解總結如下幾點ide

 1.每一個線程讀擁有本身的局部變量函數

     每一個線程都有一個獨立於其餘線程的上下文來保存這個變量,一個線程的本地變量對其餘線程是不可見的oop

 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自己咱們在平常項目開發中使用的比較少,一會半會的很難找到合適的場景來搞懂它。

相關文章
相關標籤/搜索