Java源碼:ThreadLocal

一、個人見解
通俗來說,實例變量就是在每個具體實例對象級別的變量,類變量(靜態變量)就是在類級別的變量,類似的,線程本地變量就是在線程級別存放的變量,而ThreadLocal就是用來提供線程本地變量存取的工具。在網上各種搜,結合自己的理解,覺得ThreadLocal適合用戶以下場景(純屬個人見解,有不對地方或有更好的使用場景請賜教哈):
  1. 訪問線程不安全對象。比如下面SimpleDateFormat的使用,這是個線程不安全的類,不想每次都new一個對象用完即棄?把它放進ThreadLocal裏吧,這樣每個線程就只擁有一個實例了。
  2. 存放線程級別的狀態對象。例如上下文對象、用戶會話這種對象,不想在各個方法調用中層層傳遞?把它放進ThreadLocal裏面把,這樣在同一個線程任何一個地方都可以獲取。

 二、示例

ThreadLocal的使用比較簡單,創建一個ThreadLocal對象,重寫initialValue方法,返回需要存儲的變量,並且把這個ThreadLocal對象聲明爲靜態的(因爲後續讀取線程本地變量的時候需要用這個ThreadLocal對象),需要使用的時候只要調用ThreadLocal對象的get方法就行了:
public class ThreadLocalTest {

    public static void main(String[] args) throws InterruptedException {
        new MyThread().start();
        Thread.sleep(2000);
        new MyThread().start();
    }

}

class MyThread extends Thread {
    //SimpleDateFormat爲線程不安全的,因此使用ThreadLocal在每個線程保存一個實例
    public static final ThreadLocal<SimpleDateFormat> DATE_FORMATER = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };

    //模擬放入一個上下文對象,可以在當前線程內進行讀寫
    public static final ThreadLocal<Map<String, String>> CONTEXT = new ThreadLocal<Map<String, String>>() {
        @Override
        protected Map<String, String> initialValue() {
            return new HashMap<String, String>();
        }
    };

    @Override
    public void run() {
        CONTEXT.get().put("id", "Thread_" + System.currentTimeMillis());
        try {
            while (true) {
                String id = CONTEXT.get().get("id");
                System.out.println(id + ": " + DATE_FORMATER.get().format(new Date()));
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 

  三、源碼

  1. 首先看Thread類,每個Thread都有一個變量threadLocals,存放所有對應這個線程的ThreadLocal。他的類型是ThreadLocal.ThreadLocalMap,其實就相當與一個特殊的Map,Key是ThreadLocal對象,value就是我們需要存放的線程本地變量,這裏就不對這個對象深入研究了。
    public class Thread implements Runnable {
    
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
        
        /* ...... */
    }
     
  2. 回到ThreadLocal,看看用來獲取保存的線程本地變量的get方法,其實操作的是當前線程的threadLocals:
        public T get() {
            //獲取當前線程
            Thread t = Thread.currentThread();
            //把線程的threadLocals變量拿出來
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                //通過ThreadLocal對象自身作爲key去拿出Entry
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    //找到了,就把值返回
                    return (T)e.value;
            }
            //如果threadLocals變量爲空,則需要初始化
            return setInitialValue();
     
  3. 在get方法的最後,調用了setInitialValue(),繼續看看源碼:
    private T setInitialValue() {
        //調用initialValue方法獲取初始值,這就是爲什麼我們創建ThreadLocal
        //對象的時候需要重寫這個方法來提供我們需要保存的值
        T value = initialValue();
        //獲取當前線程
        Thread t = Thread.currentThread();
        //獲取線程裏面的threadLocals對象
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //把當前ThreadLocal對象作爲key,初始值作爲value,放進去
            map.set(this, value);
        else
            //如果這個屬性爲空,則初始化並把相應的值放進去
            createMap(t, value);
        return value;
    }
     
  4. 有get就要有set,理解了setInitialValue,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);

 四、InheritableThreadLocal

看完了ThreadLocal,其實Java還提供了一個能實現類似功能的類:InheritableThreadLocal。兩者的區別是:使用InheritableThreadLocal保存線程本地變量的話,能被子線程共享。打開Thread類的構造方法,在它調用的init方法裏面有下面一段代碼:
if (parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
 恩,沒錯,其實就是在父線程創建子線程的時候,把inheritableThreadLocals變量的值全部複製給子線程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章