【併發程式設計】ThreadLocal的兄弟InheritableThreadLocal
本部落格系列是學習併發程式設計過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。
併發程式設計系列部落格傳送門
引子
public class InheritableThreadLocalDemo { private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { threadLocal.set("mainThread"); System.out.println("value:"+threadLocal.get()); Thread thread = new Thread(new Runnable() { @Override public void run() { String value = threadLocal.get(); System.out.println("value:"+value); } }); thread.start(); } }
上面程式碼中在主執行緒中設定了一個ThreadLocal變數,並將其值設定為mainThread
。然後有在主執行緒中開啟了一個子執行緒thread
,並試圖獲取在主執行緒中set的ThreadLocal變數的值。但是結果如下:
value:mainThread
value:null
通過前面的文章介紹,對於上面的結果我們也就非常容易理解了。每個執行緒都會有一個自己的ThreadLocalMap,所以子執行緒在呼叫get方法拿值的時候其實訪問的是自己的ThreadLocalMap,這個Map和主執行緒的Map是兩個不同的物件,所以肯定是拿不到值的。
那麼Java中有沒有類似的物件能實現上面的功能呢?有,InheritableThreadLocal
InheritableThreadLocal簡單使用
還是以上面的列子為列,我們只需要將ThreadLocal變成InheritableThreadLocal就行了。
public class InheritableThreadLocalDemo { private static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); public static void main(String[] args) { threadLocal.set("mainThread"); System.out.println("value:"+threadLocal.get()); Thread thread = new Thread(new Runnable() { @Override public void run() { String value = threadLocal.get(); System.out.println("value:"+value); } }); thread.start(); } }
執行結果如下:
value:mainThread
value:mainThread
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);
}
}
這個類繼承了ThreadLocal,並且重寫了getMap和createMap方法,區別就是將 ThreadLocal 中的 threadLocals 換成了 inheritableThreadLocals,這兩個變數都是ThreadLocalMap型別,並且都是Thread類的屬性。
下面就一步步來看下InheritableThreadLocal為什麼能拿到父執行緒中的ThreadLocal值。
step1:InheritableThreadLocal獲取值先呼叫了get方法,所以我們直接看看get方法都做了些啥。
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();
}
從上面的程式碼可以看出,get方法和ThreadLocal中是一樣的,唯一有區別的就是其中的getMap方法重寫了,返回的是inheritableThreadLocals屬性。這個屬性也是一個ThreadLocalMap型別的變數。那麼從這邊就可以推斷出來:肯定是在某處將父執行緒中的ThreadLocal值賦值到了子執行緒的inheritableThreadLocals中。
step2:在原始碼中搜索哪些地方使用到了inheritableThreadLocals
這個屬性,最後找到這段程式碼:
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
//1. 這邊先判斷了父執行緒中inheritableThreadLocals屬性是否為空,不為空的話就複製給子執行緒
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
上面的程式碼印證了我們的猜想。需要注意的是一旦子執行緒被建立以後,再操作父執行緒中的ThreadLocal變數,那麼子執行緒是不能感知的。因為父執行緒和子執行緒還是擁有各自的ThreadLocalMap,只是在建立子執行緒的“一剎那”將父執行緒的ThreadLocalMap複製給子執行緒,後續兩者就沒啥關係了