1. 程式人生 > >ThreadLocal<T> 原始碼解析

ThreadLocal<T> 原始碼解析

在activeJDBC框架內部的實現中看到了 ThreadLocal 這個類,記錄下了每個執行緒獨有的連線

private static final ThreadLocal<HashMap<String, Connection>> connectionsTL = new 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).

這個鳥文,瞎翻譯一下,就是:

這個類提供了供執行緒專享的變數。這些變數不同與其它普通的變數,它是每個執行緒都有一個自己的獨立初始化的變數(通過get和set方法實現)。這個類的例項常用於類的私有靜態欄位,以實現每個執行緒都有自己的狀態(例如userId,事務ID等)。

先跑一下用法吧,

package com.test.threadlocal;

public class TestController {
    
    private static int index = 0;
    private static String str = "這個字串是每個執行緒共享的";
    // 這個變數,看似是一個類的靜態屬性,實則是每個執行緒有自己獨有的區域
    private static ThreadLocal<String> threadStr = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "main執行緒專享";
        }
    };      
    
    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 3; i++) {
            Thread t = new MyThread();
            t.start();
            t.join();
        }
        
        System.out.println(str);
        System.out.println(threadStr.get());
    }
    
    static class MyThread extends Thread{

        @Override
        public void run() {
            index++;
            str = "第" + index + "個str";
            threadStr.set("第" + index + "個threadStr");
        }
        
        
    }

}

這個例子中,從str和threadStr變數的列印結果可以看出來。str被所有的執行緒讀和寫,threadStr在每個執行緒內部開闢了一塊執行緒專享的區域。接下來,我們看一下具體實現。
先看一下建構函式

     /**
     * ThreadLocals rely on per-thread linear-probe hash maps attached
     * to each thread (Thread.threadLocals and
     * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     * searched via threadLocalHashCode.  This is a custom hash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();
     /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

建構函式是空的,但是,該類有一個私有整型常量threadLocalHashCode。nextHashCode()方法我們就不看了,省的一如原始碼深似海。看鳥文的話,大概就是每new一個ThreadLocal變數的時候,就會生成一個雜湊碼,該碼非極端情況下與某個整數取模後不容易衝突(這句話有點迷吧,其實我也不懂)
然後看一下set方法

/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

容易看出,這個方法設定每個執行緒自己的value,相當於當前執行緒是key,然後得出一個ThreadLocalMap。顯然,這個map用來儲存執行緒內部的值,既然是map當然每個執行緒可以儲存多個數值了,該map的value我們猜一下就是我要儲存的具體的值,估計是用Object類宣告的。那key是什麼呢?我們看下ThreadLocalMap類的構造方法。

/**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
                
/**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        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);
        }

我的天!這個類沒有繼承我們想象中的HashMap,或者是ConcurrentMap,但是看過,Map的內部實現的同學應該可以發現,這個Map的實現和HashMap的實現簡直就是小巫見大巫,有沒有。它在建構函式中做了如下幾步:

  1. 初始化一個大小為16的Entry陣列
  2. 通過上面說過的很迷的HashCode雜湊值與15取模得到將要儲存在陣列中的索引值
  3. 構造Entry,然後儲存進去
  4. 長度設定為1
  5. 設定要擴容的限制大小為16的2/3

我們看到這個不就是用陣列實現的Map嘛,看過HashMap實現的我們,覺得灑灑水啦。
Map的set和get方法就不分析了。ThreadLocal的get方法我們還是要貼出來的,畢竟是我們主要分析的東西

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

可見,是獲取到當前執行緒,用作key獲取到Map,然後用當前this獲取到Entry實體。最後當然獲取到了儲存的value。

我編碼,我快樂~

本文由部落格一文多發平臺 OpenWrite 釋出!