Java基礎之ThreadLocal的使用及源碼解析

ThreadLocal是什麼

ThreadLocal是一個能創建線程局部變量的類。通過ThreadLocal提供的get和set方法,可以爲每一個使用該變量的線程保存一份數據副本,且線程之間是不能相互訪問的,從而達到變量在線程間隔離、封閉的效果

使用例子

public static void main(String[] args) throws InterruptedException {

    final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("AAA");

    new Thread(new Runnable() {
        @Override
        public void run() {
            threadLocal.set("BBB");
            System.out.println("get in " + Thread.currentThread().getName() + " " + threadLocal.get());
        }
    }).start();

    Thread.sleep(1000);
    System.out.println("get in main thread " + threadLocal.get());
}

執行結果:

get in Thread-0 BBB
get in main thread AAA

首先,在主線程中初始化了ThreadLocal,並且操作的變量是String類型,在主線程中設置該變量爲"AAA",主線程等待1秒鐘,同時啓動了一個子線程也調用ThreadLocal設置該變量爲"BBB"並輸出,1秒之後通過get輸出主線程的結果,發現子線程設置的值並沒有影響主線程中設置的值,即通過ThreadLocal修飾的變量可以實現在各個線程之間互不干擾,相互隔離的效果。

源碼解析

初始化

//1
final ThreadLocal<String> threadLocal = new ThreadLocal<>();

threadLocal.set("AAA");
//2
final ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "AAA";
    }
};

對應的源碼:

protected T initialValue() {
    return null;
}

public ThreadLocal() {
}

ThreadLocal的初始化可以有上面1、2兩種方式,一種是先初始化然後通過set設置值,一種直接重寫initialValue並設置值。既然ThreadLocal可以做到變量的線程封閉,我們有理由猜想是不是ThreadLocal是通過Map<Thread,T>來實現的呢?其中key是當前Thread,value是通過set或者initialValue設置的,看似是這樣,但ThreadLocal內部並不是這麼實現的,接着往下分析。

set值

public void set(T value) {
    //獲取當前線程
    Thread t = Thread.currentThread();
    //根據當前線程獲取ThreadLocalMap,注:ThreadLocalMap內部並不是通過map來存儲value,而是通過數組存儲的
    ThreadLocalMap map = getMap(t);
    if (map != null)
       //不爲空,內部直接通過數組設置Entry元素(Entry中包裝了ThreadLocal及value,其中key=ThreadLocal,value=傳入值value)
        map.set(this, value);
    else
        //爲空,則初始化一個ThreadLocalMap,並將ThreadLocal及value包裝成Entry放入數組中。
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    //threadLocals是Thread類中的成員變量
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Thread類:

public class Thread implements Runnable {

     /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
     ThreadLocal.ThreadLocalMap threadLocals = null;
   }  

所以set方法首先根據當前線程獲取線程中的threadLocals變量(ThreadLocalMap類型),並將ThreadLocal及value包裝成Entry放入數組中,因爲threadLocals是Thread中的局部變量(存放在棧空間中),所以只有當前線程能訪問,其他線程無法訪問。這裏有個問題:爲什麼還需要將ThreadLocal作爲key傳入到ThreadLocalMap呢?因爲一個線程中可以初始化多個ThreadLocal,是一對多的關係,所以需要傳入ThreadLocal,如果初始化了多個ThreadLocal,根據不同的ThreadLocal可以獲得對應的value。那麼ThreadLocalMap內部到底是怎麼存儲的呢?

ThreadLocal靜態內部類ThreadLocalMap:

static class ThreadLocalMap {

    //內部類Entry,繼承了弱引用WeakReference,使用ThreadLocal作爲鍵值
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    //初始容量 必須是2個倍數
    private static final int INITIAL_CAPACITY = 16;

    //Entry數組,必要時可以擴容,
    private Entry[] table;

    //數組大小
    private int size = 0;

    //初始化ThreadLocalMap,並將ThreadLocal、firstValue封裝成Entry並放入Entry數組中
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
   
    //根據key(ThreadLocal類型)的hash獲取Entry在數組中的位置,有數據的話直接返回該數據
    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);
    }

    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

    //根據key(ThreadLocal類型)設置value值
    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();
            //如果key值在Entry中存在,那麼直接覆蓋之前的值
            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對應的value
    private void remove(ThreadLocal<?> key) {
        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)]) {
            if (e.get() == key) {
                e.clear();
                expungeStaleEntry(i);
                return;
            }
        }
    }
}

get值

public T get() {
    //獲取當前thread
    Thread t = Thread.currentThread();
    //根據當前線程獲取ThreadLocalMap,注:ThreadLocalMap內部並不是通過map來存儲value,而是通過數組存儲的
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //根據this(ThreeadLocal)獲取數組中對應的Entry,不爲空直接取出value
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    //如果線程中的ThreadLocalMap爲空,則進行初始化
    return setInitialValue();
}

private T setInitialValue() {
    //初始化值 默認是null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

ThreadLocal在Handler中的使用

Handler機制:

https://upload-images.jianshu.io/upload_images/587163-894d1f0e7c5d4920.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200

Handler構造函數:

public Handler(Callback callback, boolean async) {
    ......其他代碼......
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Looper.prepare初始化:

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

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

Android中Handler機制在項目中使用的很頻繁,Handler底層通過MessageQueue和Looper來實現消息的線程間通信。其中Handler來發送及接收並處理消息,MessageQueue接收Handler發來的消息,並在Looper循環中根據msg.target(handler)來分發消息。一個線程只對應一個Lopper,一個Looper對應一個MessageQueue,但是一個線程中可以有多個Handler。因爲一個線程只能對應一個Looper,且Looper跟線程是一一綁定關係,此時用ThreadLocal再合適不過。

Looper中使用ThreadLocal關聯Looper,使得Looper只能在各自線程使用,並且不管handler從哪個線程傳來消息,ThreadLocal保證了最終消息在Looper初始化時所在的線程處理。

總結

  • ThreadLocal存儲變量副本實際是保存在每個線程的threadLocals(ThreadLocal.ThreadLocalMap類型)變量中。
  • ThreadLocal包含的對象(指的是ThreadLocal中的T對象)在不同的線程中有不同的副本(實際上也是不同的實例)
  • ThreadLocalMap中的Entry弱引用於ThreadLocal,同時也會回收key爲null的Entry,從而避免了Entry無法釋放導致內存泄漏

畫一個簡易圖:

threadLocal

參考

【1】https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
【2】https://www.cnblogs.com/dolphin0520/p/3920407.html
【3】http://www.jasongj.com/java/threadlocal/
【4】https://juejin.im/post/5ba64dcee51d4543e609656d

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章