1. 程式人生 > 實用技巧 >Java ThreadLocal 原理分析

Java ThreadLocal 原理分析

原作者:https://www.jianshu.com/p/c64f06f0823b

ThreadLocal提供了執行緒本地變數,它可以保證訪問到的變數屬於當前執行緒,每個執行緒都儲存有一個變數副本,每個執行緒的變數都不同。ThreadLocal相當於提供了一種執行緒隔離,將變數與執行緒相繫結。

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

ThreadLocal 類定義如下:

public class ThreadLocal<T> {

    private final int threadLocalHashCode = nextHashCode();

    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    
    public ThreadLocal() {
    }
}

ThreadLocal通過threadLocalHashCode來標識每一個ThreadLocal的唯一性。threadLocalHashCode通過CAS操作進行更新,每次hash操作的增量為 0x61c88647(不知為何)。

接下來,看看它的set方法:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

通過Thread.currentThread()方法獲取了當前的執行緒引用,並傳給了getMap(Thread)方法獲取一個ThreadLocalMap的例項。我們繼續跟進getMap(Thread)方法:

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看到getMap(Thread)方法直接返回Thread例項的成員變數threadLocals。它的定義在Thread內部,訪問級別為package級別:


public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

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

到了這裡,我們可以看出,每個Thread裡面都有一個ThreadLocal.ThreadLocalMap成員變數,也就是說每個執行緒通過ThreadLocal.ThreadLocalMap與ThreadLocal相繫結,這樣可以確保每個執行緒訪問到的thread-local variable都是本執行緒的。

我們往下繼續分析。獲取了ThreadLocalMap例項以後,如果它不為空則呼叫ThreadLocalMap.ThreadLocalMap 的set方法設值;若為空則呼叫ThreadLocal 的createMap方法new一個ThreadLocalMap例項並賦給Thread.threadLocals。

ThreadLocal 的 createMap方法的原始碼如下:

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

ThreadLocal 的 get 方法,原始碼如下:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

通過Thread.currentThread()方法獲取了當前的執行緒引用,並傳給了getMap(Thread)方法獲取一個ThreadLocalMap的例項。繼續跟進setInitialValue()方法:

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

首先呼叫 initialValue()方法來初始化,然後 通過Thread.currentThread()方法獲取了當前的執行緒引用,並傳給了getMap(Thread)方法獲取一個ThreadLocalMap的例項,並將 初始化值存到ThreadLocalMap 中。

initialValue() 原始碼如下:

  protected T initialValue() {
        return null;
    }

下面我們探究一下ThreadLocalMap的實現。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的靜態內部類,原始碼如下:

public class ThreadLocal<T> {

    static class ThreadLocalMap {

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

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

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        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);
        }
    }
    
}

其中INITIAL_CAPACITY代表這個Map的初始容量;1是一個Entry型別的陣列,用於儲存資料;size代表表中的儲存數目;threshold代表需要擴容時對應size的閾值。

Entry類是ThreadLocalMap的靜態內部類,用於儲存資料。

Entry類繼承了WeakReference<ThreadLocal<?>>,即每個Entry物件都有一個ThreadLocal的弱引用(作為key),這是為了防止記憶體洩露。一旦執行緒結束,key變為一個不可達的物件,這個Entry就可以被GC了。

接下來我們來看ThreadLocalMap 的set方法的實現:

        private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            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();

                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();
        }

ThreadLocal 的get方法會呼叫 ThreadLocalMap 的 getEntry(ThreadLocal key) ,其原始碼如下:

    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;
    }

參考資料

併發程式設計 | ThreadLocal原始碼深入分析



作者:FX_SKY
連結:https://www.jianshu.com/p/c64f06f0823b
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。