以前有介紹過 ThreadLocal,JDK 後來針對此作了一個升級版本 InheritableThreadLocal,今天就來好好介紹下。
java
首先咱們來想一想,爲何要升級?這就要提及 ThreadLocal 的功能了。git
咱們知道,ThreadLocal 設計初衷是爲了在多線程環境下,針對每個線程能有一個本身的副本,這樣能夠在必定程度上解決多線程併發修改的問題。可是,咱們能夠在此基礎上作一個拓展,好比context
,咱們能夠利用 ThreadLocal 針對每個線程都有一個本身的上下文,通常都是寫成ThreadLocal<Context>
,這樣在這個線程上作的全部修改均可以被你們利用到。github
此時設想一下,假如咱們新建一個子線程,那這個子線程能夠獲取到父線程的context
嗎?理論上但願能夠達成這樣的效果,實際上呢?讓咱們看看:多線程
public class ThreadLocalContext { private static ThreadLocal<Context> context = new ThreadLocal<>(); static class Context { String name; int value; } public static void main(String[] args) { Context context = new Context(); context.name = "mainName"; context.value = 10; ThreadLocalContext.context.set(context); Thread childThread = new Thread( new Runnable() { @Override public void run() { Context childContext = ThreadLocalContext.context.get(); System.out.println(childContext.name); System.out.println(childContext.value); } } ); childThread.start(); } }
運行 main 方法以後,直接在子線程中拋錯,這樣確實符合咱們的預期,但若是咱們想達到子線程能夠獲取到父線程的 context
這樣的效果該如何作呢?併發
首先想到的就是在生成子線程的時候,將父線程 ThreadLocal 裏的值傳給子線程。這樣作雖然能達到效果,但過程比較繁雜,且代碼侵入性強。ide
這個時候就能夠用InheritableThreadLocal
了。學習
先讓咱們看看它的源碼,你們不要怕,它的源碼不多:this
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); } }
首先它繼承自 ThreadLocal,那麼它其實就是 ThreadLocal 的一個拓展版本,接下來就是這三個方法,其實這三個方法在 ThreadLocal 都是有的,咱們來看看:線程
T childValue(T parentValue) { throw new UnsupportedOperationException(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
除了childValue
方法在 ThreadLocal 中是拋出異常的,其他兩個方法在兩個類中都幾乎是同樣,只是針對的對象不一樣而已,但threadLocals
和inheritableThreadLocals
都是ThreadLocal.ThreadLocalMap
類型,這個在以前的文章中有說過,就是一個 key 爲弱引用
的 Entry,這個倒不是重點。設計
咱們再來看看 inheritableThreadLocals 是在什麼時候被初始化的,從源碼能夠得知:
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { // 省略無關代碼 ... Thread parent = currentThread(); ... // 省略無關代碼 ... if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ... }
當咱們經過父線程調用 Thread 的構造方法生成一個子線程時,其構造方法最終會調用這個 init 方法。從這兒能夠看出, inheritableThreadLocals 是來自於父線程的 inheritableThreadLocals,那這樣也就解釋了爲何 inheritableThreadLocals 支持在子線程中使用父線程中存儲的變量。
讓咱們仍是回到上文提到的 context 的例子,用 InheritableThreadLocal 進行改造:
public class ThreadLocalContext { private static InheritableThreadLocal<Context> context = new InheritableThreadLocal<>(); static class Context { String name; int value; } public static void main(String[] args) { Context context = new Context(); context.name = "mainName"; context.value = 10; ThreadLocalContext.context.set(context); Thread childThread = new Thread( new Runnable() { @Override public void run() { Context childContext = ThreadLocalContext.context.get(); System.out.println(childContext.name); System.out.println(childContext.value); } } ); childThread.start(); } }
運行後,不只沒有拋出異常,並且在子線程中輸出了父線程設置好的值。皆大歡喜!
今天分享了 InheritableThreadLocal,主要是由於週三在攜程的分享會上聽到了別人談了這方面的分享,主講人講了一個更加廣泛的問題,若是咱們用線程池提交任務的話,線程池中的線程在執行任務時,如何可以得到提交任務的線程的 context,這時就要用到阿里的開源組件 TTL,我會在以後進行介紹。
加入攜程也有1個月了,雖然感覺到大公司有很多的弊端,好比溝通難等,但也有很多的優勢,好比技術分享會,雖然也是忙裏偷閒去參加的,但有了更多和技術相關的能夠學習和交流的機會,也挺好的。
有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。
公衆號:健程之道