1. 程式人生 > >Java ThreadLocal 的使用與原始碼解析

Java ThreadLocal 的使用與原始碼解析

GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用與原始碼解析/

ThreadLocal 主要解決的是每個執行緒繫結自己的值,可以將 ThreadLocal 看成全域性存放資料的盒子,盒子中儲存每個執行緒的私有資料。

驗證執行緒變數的隔離性

import static java.lang.System.out;

public class Run {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    static class Work extends Thread {

        @Override
        public void run() {
            threadLocal.set(0);
            for (int i = 1; i <= 5; i++) {
                // 獲取資料
                int sum = threadLocal.get();
                out.printf("%s's sum = %s\n", getName(), threadLocal.get());
                sum += i;
                // 寫回資料
                threadLocal.set(sum);
            }
            out.printf("END %s's sum = %d\n\n", getName(), threadLocal.get());
        }
    }

    public static void main(String[] args) {
        Work work1 = new Work(),
                work2 = new Work();

        work1.start();
        work2.start();
    }
}

執行結果:

Thread-0's sum = null
Thread-1's sum = null
Thread-1's sum = 1
Thread-1's sum = 3
Thread-1's sum = 6
Thread-1's sum = 10
END Thread-1's sum = 15

Thread-0's sum = 1
Thread-0's sum = 3
Thread-0's sum = 6
Thread-0's sum = 10
END Thread-0's sum = 15


Process finished with exit code 0

從結果來看,兩個執行緒的計算結果一致,ThreadLocal

中隔離了兩個執行緒的資料。

ThreadLocal 原始碼解析

ThreadLocalMap 內部類

ThreadLocal 中有一個 ThreadLocalMap 內部類,所以 ThreadLocal 實際上是使用一個雜湊表來儲存每個執行緒的資料的。

ThreadLocalMapHashMap 不同,其中 Entry 是一個弱引用,這意味著每次垃圾回收執行時都會將儲存的資料回收掉。而且它只使用了陣列來儲存鍵值對。

ThreadLocalMap 中的 Entry

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

get() 方法

public T get() {
    // 得到當前執行緒
    Thread t = Thread.currentThread();
    // 獲取當前執行緒的雜湊表
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 從雜湊表中獲取當前執行緒的資料
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

get() 方法首先得到當前執行緒,然後獲取當前執行緒的 ThreadLocalMap 物件,然後從中取出資料。

這裡的 map.getEntry(this) 看起來很奇怪,在前面有這樣一行程式碼:

ThreadLocalMap map = getMap(t);

這個方法獲取當前執行緒的 ThreadLocalMap 物件,所以,雖然 map.getEntry() 中的 key 總是 ThreadLocal 物件本身,但是每個執行緒都持有有自己的 ThreadLocalMap 物件。

getMap() 方法

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

看到這個方法,get() 方法中 map.getEntry(this) 的迷霧就解開了。這裡可以看到返回的是執行緒中的 threadLocals 屬性。那麼這裡瞟一眼 Thread 的原始碼:

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

其實每次 get() 時都是先獲取了執行緒自己的 ThreadLocalMap 物件,然後對這個物件進行操作。

set() 方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        // 為當前執行緒建立一個 ThreadLocalMap 物件
        createMap(t, value);
}

set() 方法也是先獲取當前執行緒自己的 ThreadLocalMap 物件,然後再設定資料。如果獲取的雜湊表為 null,則建立一個。

createMap() 方法

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

createMap() 方法建立一個 ThreadLocalMap 物件,該物件由執行緒持有。

總結

  • ThreadLocal 可以隔離執行緒的變數,每個執行緒只能從這個物件中獲取到屬於自己的資料。
  • ThreadLocal 使用雜湊表來儲存執行緒的資料,而且這個雜湊表是由執行緒自己持有的,每次獲取和設值都會先獲取當前執行緒持有的ThreadLocalMap 物件。
  • ThreadLocalMap 中的 key 總是 ThreadLocal 物件本身。
  • ThreadLocalMap 中的 Entry 是弱引用,每次 GC 執行都會被回收。