最近項目中爲了讓打印的日誌能夠追蹤,看到了Logback和log4j2支持的MDC功能,其內部利用了子線程從父線程繼承InheritableThreadLocal類型變量的特性。之前只使用到了ThreadLocal,在這裏作一下對比,並簡單的作了一些分析java
ThreadLocal聲明的變量是線程私有的成員變量,每一個線程都有該變量的副本,線程對變量的修改對其餘線程不可見數組
InheritableThreadLocal聲明的變量一樣是線程私有的,可是子線程可使用一樣的InheritableThreadLocal類型變量從父線程繼承InheritableThreadLocal聲明的變量,父線程沒法拿到其子線程的。即便能夠繼承,可是子線程對變量的修改對父線程也是不可見的。ide
Thread有一個類型爲ThreadLocal.ThreadLocalMap變量:threadLocals,ThreadLocalMap底層是一個Entry數組,用於保存ThreadLocal引用和真正變量(程序中銷售人的主管姓名)的值測試
看下面的代碼和註釋瞭解如何將值放入threadLocals的this
調用ThreadLocal對象的set()方法spa
public void set(T value) { //獲取到當前線程對象 Thread t = Thread.currentThread(); //從當前線程對象中拿threadLocals變量 ThreadLocalMap map = getMap(t); //若是threadLocals不爲空,則調用它的set方法,將key=當前ThreadLocal對象 value=主管姓名 if (map != null) map.set(this, value); //若是threadLocals爲空,則新建立一個ThreadLocalMap else createMap(t, value); }
InheritableThreadLocal繼承自ThreadLocal,所以能夠想到它本身要麼重寫,要麼新增了部分方法。從源碼看只是重寫了childValue、getMap、createMap方法,其中getMap和createMap都是父類set方法中使用到的,所以主要是改變了父類set方法的行爲。線程
getMap獲取的是InheritableThreadLocal類型的變量日誌
createMap建立的是InheritableThreadLocal類型的實例對象
可是從這點上看是不能達到子線程繼承父線程的變量的。再看看Thread的init方法中比之前新增了以下代碼:blog
//若是當前線程的InheritableThreadLocal類型變量inheritThreadLocals爲空且父線程的 //inheritThreadLocals不爲空,則複製父線程的inheritThreadLocals中的內容給當前線程的 //inheritThreadLocals,是值複製不是引用賦值 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
上面說到重寫了三個方法,其中的childValue尚未找到使用的地方,如今該它出場了,就是在繼承變量的時候,建立新的ThreadLocalMap使用到了。
createInheritedMap->ThreadLocalMap->{
//建立一個新的Entry數組
table = new Entry[len];
//複製數據
Object value = key.childValue(e.value); Entry c = new Entry(key, value); table[h] = c;
}
寫一個銷售人類,該類有一個主管人員成員變量(ThreadLocal聲明),將銷售人對象傳給不一樣的工做線程,在工做線程中打印銷售人的主管,其結果應該是爲NULL,由於主管是聲明爲線程本地變量,是不能共享的
package com.jv.jdk.thread.threadlocal; import lombok.Data; @Data public class Salesman { private ThreadLocal<String> charger = new ThreadLocal<>(); public Salesman(String charger){ this.charger.set(charger); } }
package com.jv.jdk.thread.threadlocal; import lombok.extern.log4j.Log4j2; //@Log4j2註解能夠很方便作日誌輸出 import java.util.concurrent.CountDownLatch; @Log4j2 public class Worker implements Runnable{ private Salesman salesman = null; private CountDownLatch countDownLatch = null; public Worker(Salesman salesman, CountDownLatch countDownLatch){ this.salesman = salesman; this.countDownLatch = countDownLatch; } @Override public void run() { log.info(Thread.currentThread().getName()+":"+salesman.getCharger().get()); countDownLatch.countDown(); } }
測試類
@Test public void threadLocal(){ CountDownLatch countDownLatch = new CountDownLatch(2); Salesman salesman = new Salesman("劉牆東"); Thread thread_1 = new Thread(new Worker(salesman,countDownLatch)); Thread thread_2 = new Thread(new Worker(salesman,countDownLatch)); thread_1.start(); thread_2.start(); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } }
測試結果:
2018.08.30 at 14:47:45 CST 14-Thread-1 INFO com.jv.jdk.thread.threadlocal.Worker run() @20 - Thread-1:null 2018.08.30 at 14:47:45 CST 15-Thread-2 INFO com.jv.jdk.thread.threadlocal.Worker run() @20 - Thread-2:null
與上面的代碼重寫SalesMan類
package com.jv.jdk.thread.threadlocal; import lombok.Data; @Data public class SalesmanInherit extends Salesman{ private InheritableThreadLocal<String> charger = new InheritableThreadLocal<>(); public SalesmanInherit(String charger){ super(charger); this.charger.set(charger); } }
@Test public void inheritablThreadLocal(){ CountDownLatch countDownLatch = new CountDownLatch(2); Salesman salesman = new Salesman("劉牆東"); SalesmanInherit salesmanInherit = new SalesmanInherit("劉牆東"); Thread thread_1 = new Thread(new Worker(salesmanInherit,countDownLatch)); Thread thread_2 = new Thread(new Worker(salesmanInherit,countDownLatch)); thread_1.start(); thread_2.start(); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } }
測試結果:
2018.08.30 at 14:51:28 CST 15-Thread-2 INFO com.jv.jdk.thread.threadlocal.Worker run() @20 - Thread-2:劉牆東 2018.08.30 at 14:51:28 CST 14-Thread-1 INFO com.jv.jdk.thread.threadlocal.Worker run() @20 - Thread-1:劉牆東