ThreadLocal源碼解析以及InheritableThreadLocal拓展

ThreadLocal

1 ThreadLocal的簡介

在不少場景下,咱們想要傳遞參數必須經過顯式參數定義,可是方法棧層次更加深的時候,顯的特別不優雅,例如傳遞用戶信息到調用的方法中:java

//controller傳遞用戶信息到serviceA
controller.serviceA(user);

//繼續向serviceB傳遞
serviceA.serviceB(user);

//最終傳遞到dao層
serviceB.dao(user);
複製代碼

每個方法都要定義一個顯式的用戶參數,顯得很是臃腫,有沒有其餘辦法呢?數組

有人可能想到了定義一個公共的屬性或者靜態變量,可是這樣會引起一個多線程共享變量線程不安全問題,因此必須對這個公共屬性進行加鎖控制。安全

一旦上鎖,那效率可不是慢了一星半點,有沒有更加高效的辦法呢?這時候就要用到***ThreadLocal***了。bash

上面提到的同步變量,採起的是統一治理,而ThreadLocal採起的策略是分而治之。多線程

用官方的話來講:ThreadLocal類用來提供線程內部的局部變量。這種變量在多線程環境下訪問(經過get或set方法訪問)時能保證各個線程裏的變量相對獨立於其餘線程內的變量。ThreadLocal實例一般來講都是private static類型的,用於關聯線程和線程的上下文。併發

簡單的說:源碼分析

  • ThreadLocal提供了一個線程內部的變量副本,這個變量只在單個線程內部共享,在該線程內能夠方便的訪問ThreadLocal變量副本。post

  • 多個線程間的TreadLocal變量副本互不影響。this

  • ThreadLocal只存活在線程的生命週期內,隨着線程消亡而消亡(也能夠手動調用remove方法移除ThreadLocal變量)。spa

這樣一來就優雅的解決了單個線程中不一樣方法傳遞參數的複雜度問題,由於是每一個線程內部共享變量,也不存在多線程不安全的問題了。

須要注意的是:

  • ThreadLocal不是同步機制,不解決多線程下的線程安全問題。

  • ThreadLocal爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的併發訪問衝突。由於每個線程都擁有本身的變量副本,也就不存在多線程不安全的問題了。

咱們上面提到的傳遞用戶信息的問題能夠經過ThreadLocal實現:

public final class UserUtil {
	private static ThreadLocal<User> userThreadLocal = new ThreadLocal();
  
 	public static User getUser() {
	    return userThreadLocal.get();
	}
 	public static User setUser(User user) {
	    return userThreadLocal.set(user);
	}
	public static void removeUser() {
			userThreadLocal.remove();
	} 
}
複製代碼
//設置User
controller(){
	UserUtil.setUser(user);
}

//獲取User
serviceA(){
	UserUtil.getUser();
}
serviceB(){
	UserUtil.getUser();
}
dao(){
  UserUtil.getUser();
}
複製代碼

2 ThreadLocal的實現原理

ThreadLocal爲每個線程建立了一個獨立的副本變量,那它到底怎麼實現的呢?

從ThreadLocal源碼中,能夠得知以下信息:

經過調用***get***方法獲取ThreadLocal對應的變量,***get***方法源碼以下:

public T get() {
  	//獲取當前線程
    Thread t = Thread.currentThread();
  	//根據線程獲取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      	//獲取Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
  	//設置並返回初始值
    return setInitialValue();
}
複製代碼

此方法先是獲取了當前線程,再經過當前線程獲取了一個***ThreadLocalMap*** ,若是ThreadLocalMap 爲空則設置一個初始值並返回。若是獲取的map不爲空,則根據當前的***ThreadLocal***獲取對應的value並返回。

根據這個方法咱們能夠獲取如下幾點信息:

  • ThreadLocal對應的值都是存在***ThreadLocalMap***中。

  • ThreadLocalMap是根據***當前線程***實例實例獲取的。

  • 若是***ThreadLocalMap***爲NULL或者ThreadLocal沒有對應值的狀況下,返回初始值(setInitialValue方法)。

既然知道了ThreadLocal的值是存在***ThreadLocalMap***中,咱們繼續看一下ThreadLocalMap map = getMap(t);這一段代碼的具體實現。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
複製代碼

這個方法直接返回了,線程中的一個***threadLocals***屬性,進一步跟蹤能夠發如今***Thread***類中定義了此屬性。

public class Thread implements Runnable {
	...
	
	ThreadLocal.ThreadLocalMap threadLocals = null;
	
	...
}
複製代碼

經過源碼咱們已經很是清晰的知道,Thread***中保存了***ThreadLocalMap,而***ThreadLocalMap***中保存了***ThreadLocal***對應的鍵值對,示意圖以下:

3 ThreadLocal的部分源碼解析

上一小節咱們已經分析了***get***方法的源碼,接下來咱們分析一下其餘的幾段主要源碼。

set方法:

public void set(T value) {
  	//獲取當前線程
    Thread t = Thread.currentThread();
  	//獲取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
複製代碼

set***方法的做用是將當前***ThreadLocal實例***做爲key,且將對應的value造成鍵值對保存進***ThreadLocalMap***中。若是***ThreadLocalMap***還未被建立則建立一個新的***ThreadLocalMap

建立***ThreadLocalMap***,createMap(t, value)方法具體實現以下。

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

***createMap***方法直接***new***了一個***ThreadLocalMap***對象,傳入的參數時當前的***ThreadLocal***實例以及一個須要保存的變量值,跟蹤進入***ThreadLocalMap***構造方法。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  	//建立Entry數組
    table = new Entry[INITIAL_CAPACITY];
  	//計算元素位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  	//建立Entry
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
複製代碼

***ThreadLocalMap***構造方法使用初始長度***INITIAL_CAPACITY***建立了一個***Entry***數組,並計算了初始元素的下標。

保存***ThreadLocal鍵值對***到數組對應的位置以後,設置size爲1,而且初始化擴容下限。

由此代碼可知,全部的***ThrealLocal鍵值對***最終保存的位置是一個***Entry數組***,Entry類定義在***ThreadLocalMap***類中。

public class ThreadLocal<T> {    
  ...
    static class ThreadLocalMap {
      ...
        //弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
      ...
    }
  ...
}
複製代碼

因爲Entry繼承了***WeakReference***,ThreadLocal被定義爲弱引用對象,因此只要沒有強引用指向ThreadLocal,那麼觸發GC時無論內存是否足夠,都會把ThreadLocal回收掉。

可是這個時候有個問題,若是ThreadLocal被回收掉,而對應***Entry***中的value沒有被回收,那這個value將永遠沒法訪問到了。當愈來愈多的ThreadLocal被回收,對應的產生愈來愈多value沒法被回收,最終會形成內存泄漏。

有人會說,那設計成強引用不就好了嗎?其實設計成強引用並無效果,若是沒有手動將引用設置成null,反而形成了key(ThreadLocal)和value都不被回收,最終也會形成內存泄漏。

設計成強引用還會形成value被手動設置成null,而key(ThreadLocal)一直不被回收的狀況,一樣會形成內存泄漏。

這就是爲何ThreadLocal被設計成弱引用對象,當value被手動回收,同時會把ThreadLocal也進行回收,這無疑多了一份保險。

上面的分析也是經常有人提起使用ThreadLocal以後,須要手動的調用***set()、get()、remove()***不然會發生內存泄漏。

咱們能夠看一下***remove()***是怎麼處理這個問題的。

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) {
          	//清除key
            e.clear();
          	//進一步清除
            expungeStaleEntry(i);
            return;
        }
    }
}
複製代碼

***remove()***循環了table,而且調用了***e.clear()***用來清除key。

而接下來的***expungeStaleEntry***方法作了幾件事情:

  • 把***value***和***table***中的第i個元素一塊兒清除。
  • 循環table中的元素把key等於null的元素清除
  • 從新排列table的元素位置
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
  	//設置value和tab[staleSlot]爲null
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    Entry e;
    int i;
  	//循環全部元素,並清除key==null的元素
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
      	//清除key==null的元素
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
          	//從新排列元素位置
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}
複製代碼

一樣的在ThreadLocal的***set()、get()***方法中最終也會調用***expungeStaleEntry***方法來清除key爲null的值,此處就再也不贅述了。

4 ThreadLocal的延伸

ThreadLocal是用來解決線程內的資源共享,而且作到***線程間資源隔離***,可是某些場景咱們須要在子線程中訪問主線程的資源,這是否能實現呢?固然能夠,這個時候須要用到另一個類:InheritabIeThreadLocal

在Thread源碼中咱們很容易注意到***threadLocals***屬性下方有另一個屬性***inheritableThreadLocals***。

public class Thread implements Runnable {
	...
	
	ThreadLocal.ThreadLocalMap threadLocals = null;
  
  //繼承map
	ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
	...
}
複製代碼

這兩個屬性的類型都是***ThreadLocal.ThreadLocalMap***,而***inheritableThreadLocals***是被***InheritableThreadLocal***類所引用。

咱們先看一下***InheritableThreadLocal***類的源碼

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
複製代碼

InheritableThreadLocal***繼承了***ThreadLocal,覆蓋了***childValue、getMap、createMap***這三個方法。

getMap、createMap***由原來操做***t.threadLocals***變成了操做***t.inheritableThreadLocals,而***childValue***方法會在***ThreadLocalMap***類建立繼承Map中用到。

public class ThreadLocal<T> {
  	...
    //建立一個繼承map
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
		...
    //根據parentMap建立一個新的ThreadLocalMap,其中的元素key和value值相同
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];
      	//循環建立新的Entry
        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
              	//過濾key等於null的值
                if (key != null) {
                  	//childValue返回的值便是e.value
                    Object value = key.childValue(e.value);
                  	//key,value值保持和父線程一致
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }
  ...
}
複製代碼

createInheritedMap***是根據傳入的***parentMap***複製了一個新的***ThreadLocalMap,過濾掉key等於null的值,其餘的元素key和value值和父線程保持一致。

那線程是在何時建立繼承map呢?當線程初始化時,會調用***Thread***類的***init***方法,而當咱們指定***inheritThreadLocals***爲***true***而且父類線程***inheritableThreadLocals***不等於null的時候的時候,線程則會建立繼承map。

public class Thread implements Runnable {
  	....
    //絕大部分狀況下調用本初始化方法
    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
      	//inheritThreadLocals 默認爲 true
        init(g, target, name, stackSize, null, true);
    }
  
    //線程初始化
		private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
				...
          //父類線程爲建立本線程的運行線程
          Thread parent = currentThread();
      	...
          //建立繼承map
       		if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            		this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
      	...
    }
		...
}
複製代碼

這裏有三點須要你們注意一下:

  • 絕大部分狀況下***inheritThreadLocals***默認爲true。

  • 父類線程爲建立本線程的運行線程Thread parent = currentThread(),也就是當前運行線程建立了一個新的線程,這個時候仍是運行線程在執行而新線程尚未初始化完畢,因此parent = currentThread()

  • 子線程的***inheritableThreadLocals***,是根據父線程的***inheritableThreadLocals***複製得來的,至關於把父線程***inheritableThreadLocals***值傳遞下去,這樣就實現了子線程獲取到父線程的值。

下面經過一個例子比較一下***ThreadLocal***和***InheritableThreadLocal***的實際使用。

package com.gavin.test;

public class ThreadLocalTest {

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

    public static void main(String[] args) {
      	//設置主線程的值
        ThreadLocalTest.threadLocal.set("threadLocal-main");
      	//啓用線程1
        new Thread(() -> {
          	//設置線程1的值
            ThreadLocalTest.threadLocal.set("threadLocal-1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("線程1==" + ThreadLocalTest.threadLocal.get());
        }).start();

      	//啓用線程2
        new Thread(() -> {
          	//設置線程2的值
            ThreadLocalTest.threadLocal.set("threadLocal-2");
            System.out.println("線程2==" + ThreadLocalTest.threadLocal.get());
        }).start();

        System.out.println("主線程==" + ThreadLocalTest.threadLocal.get());
    }
}

複製代碼

本段代碼定義了一個***threadLocal***屬性,而且在三個線程中都對這個屬性進行了設值,輸出結果以下:

主線程==threadLocal-main
線程2==threadLocal-2
線程1==threadLocal-1
複製代碼

由此能夠得出三個線程的threadLocal屬性互不干擾,咱們改變一下代碼,看一會兒線程是否能取到主線程的值。

package com.gavin.test;

public class ThreadLocalTest {

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

    public static void main(String[] args) {
      
        ThreadLocalTest.threadLocal.set("threadLocal-main");
        new Thread(() -> {
          	//直接打印值
            System.out.println("線程1==" + ThreadLocalTest.threadLocal.get());
        }).start();

        new Thread(() -> {
          	//直接打印值
            System.out.println("線程2==" + ThreadLocalTest.threadLocal.get());
        }).start();

        System.out.println("主線程==" + ThreadLocalTest.threadLocal.get());
    }
}

複製代碼

運行結果以下:

線程1==null
主線程==threadLocal-main
線程2==null
複製代碼

由結果能夠得出子線程並不能獲取主線程的threadLocal值,再次證實線程間的threadLocal相互隔離。

爲了實現子線程訪問主線程的值,咱們嘗試使用***inheritableThreadLocal***來實現。

package com.gavin.test;

public class InheritableThreadLocalTest {

    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-main");
        new Thread(() -> {
            System.out.println("線程1==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
        }).start();

        new Thread(() -> {
            System.out.println("線程2==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
        }).start();

        System.out.println("主線程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
    }
}

複製代碼

運行結果以下:

線程1==inheritableThreadLocal-main
主線程==inheritableThreadLocal-main
線程2==inheritableThreadLocal-main
複製代碼

由結果得出子線程獲得了主線程的值,假如子線程的值修改了,會影響主線程或者其餘子線程嗎?

根據上面的源碼分析,主線程會把本身的***inheritableThreadLocal***傳遞給子線程,子線程從新new Entry對象用來保存key和value,因此子線程修改不會影響主線程的值,也不會影響其餘子線程,只會向本身的子線程傳遞,咱們來驗證一下吧。

package com.gavin.test;

public class InheritableThreadLocalTest {

    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-main");
        new Thread(() -> {
            InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("線程1==" + InheritableThreadLocalTest.inheritableThreadLocal.get());

            new Thread(() -> {
                System.out.println("線程1的子線程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
            }).start();

        }).start();

        new Thread(() -> {
            InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-2");
            System.out.println("線程2==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
        }).start();

        System.out.println("主線程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
    }
}

複製代碼

運行結果以下

主線程==inheritableThreadLocal-main
線程2==inheritableThreadLocal-2
線程1==inheritableThreadLocal-1
線程1的子線程==inheritableThreadLocal-1
複製代碼

正如咱們猜測的那樣:

  • 子線程能夠經過***InheritableThreadLocal***獲得主線程的值(實際上是一個新的Entry對象)。
  • 子線程修改***InheritableThreadLocal***值不會影響主線程以及其餘線程的值。
  • 子線程的***InheritableThreadLocal***會向下傳遞到它的子線程。

掘金專欄:juejin.im/user/5ba21d…

做者:GavinKing

原創不易,轉載請取得做者贊成,並帶上版權信息,謝謝

相關文章
相關標籤/搜索