歡迎關注本人公衆號
之前已經介紹,InheritableThreadLocal可以在子線程創建的時候,將父線程的本地變量拷貝到子線程中。
那麼問題就來了,是隻有在創建的時候才拷貝,只拷貝一次,然後就放到線程中的inheritableThreadLocals
屬性緩存起來。由於使用了線程池,該線程可能會存活很久甚至一直存活,那麼inheritableThreadLocals
屬性將不會看到父線程的本地變量的變化
public class InheritableThreadLocalTest1 {
public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws InterruptedException {
System.out.println("主線程開啓");
threadLocal.set(1);
executorService.submit(() -> {
System.out.println("子線程讀取本地變量:" + threadLocal.get());
});
TimeUnit.SECONDS.sleep(1);
threadLocal.set(2);
executorService.submit(() -> {
System.out.println("子線程讀取本地變量:" + threadLocal.get());
});
}
}
運行結果:
主線程開啓
子線程讀取本地變量:1
子線程讀取本地變量:1
public class InheritableThreadLocalTest1 {
public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws InterruptedException {
System.out.println("主線程開啓");
threadLocal.set(1);
executorService.submit(() -> {
System.out.println("子線程讀取本地變量:" + threadLocal.get());
threadLocal.remove();
});
TimeUnit.SECONDS.sleep(1);
threadLocal.set(2);
executorService.submit(() -> {
System.out.println("子線程讀取本地變量:" + threadLocal.get());
threadLocal.set(3);
System.out.println("子線程讀取本地變量:" + threadLocal.get());
threadLocal.remove();
});
}
}
運行結果:
主線程開啓
子線程讀取本地變量:1
子線程讀取本地變量:null
子線程讀取本地變量:3
可以看到,由於兩次執行復用了同一個線程,所以即使父線程的本地變量發生了改變,子線程的本地變量依舊是首次創建線程時賦的值。
很多時候我們可能需要在提交任務到線程池時,線程池中的線程可以實時的讀取父線程的本地變量值到子線程中,當然可以當作參數傳遞如子線程,但是代碼不夠優雅,不夠美觀。此時可以使用alibaba的開源項目transmittable-thread-local
注意: 值傳遞
就像方法傳參都是值傳遞(如果是對象,則傳遞的是引用的拷貝)一樣,InheritableThreadLocal父子線程傳遞也是值傳遞!!
public class InheritableThreadLocalTest1 {
public static ThreadLocal<Stu> threadLocal = new InheritableThreadLocal<>();
public static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws InterruptedException {
System.out.println("主線程開啓");
threadLocal.set(new Stu("aaa",1));
executorService.submit(() -> {
System.out.println("子線程讀取本地變量:" + threadLocal.get());
threadLocal.get().setAge(55);
System.out.println("子線程讀取本地變量:" + threadLocal.get());
});
TimeUnit.SECONDS.sleep(1);
System.out.println("主線程讀取本地變量:" + threadLocal.get());
threadLocal.get().setAge(99);
executorService.submit(() -> {
System.out.println("子線程讀取本地變量:" + threadLocal.get());
});
}
}
輸出結果:
主線程開啓
子線程讀取本地變量:Stu(name=aaa, age=1)
子線程讀取本地變量:Stu(name=aaa, age=55)
主線程讀取本地變量:Stu(name=aaa, age=55)
子線程讀取本地變量:Stu(name=aaa, age=99)
爲什麼是值傳遞,還要從源碼分析,源碼中未進行拷貝,直接返回父線程對象的引用:
所以,務必關心傳遞對象的線程安全
問題!!
實現線程本地變量的拷貝
上面一節講到,InheritableThreadLocal是複製的對象引用,所以主子線程其實都引用的同一個對象,存在線程安全的問題。那麼如何實現對象值的複製呢?
很簡單,只需要重寫java.lang.InheritableThreadLocal#childValue
方法即可.
這裏自定義一個MyInheritableThreadLocal
類,實現對象的拷貝。
我這裏使用的序列化反序列化的方式,當然也可以用其他方式。
public class MyInheritableThreadLocal<T> extends InheritableThreadLocal<T> {
protected T childValue(T parentValue) {
String s = JSONObject.toJSONString(parentValue);
return (T)JSONObject.parseObject(s,parentValue.getClass());
}
}
將上面測試類的InheritableThreadLocal
改爲我自己定義的MyInheritableThreadLocal
類
public class InheritableThreadLocalTest1 {
public static ThreadLocal<Stu> threadLocal = new MyInheritableThreadLocal<>();
public static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws InterruptedException {
System.out.println("主線程開啓");
threadLocal.set(new Stu("aaa",1));
executorService.submit(() -> {
System.out.println("子線程讀取本地變量:" + threadLocal.get());
threadLocal.get().setAge(55);
System.out.println("子線程讀取本地變量:" + threadLocal.get());
});
TimeUnit.SECONDS.sleep(1);
System.out.println("主線程讀取本地變量:" + threadLocal.get());
threadLocal.get().setAge(99);
System.out.println("主線程讀取本地變量:" + threadLocal.get());
executorService.submit(() -> {
System.out.println("子線程讀取本地變量:" + threadLocal.get());
});
}
}
運行結果:
主線程開啓
子線程讀取本地變量:Stu(name=aaa, age=1)
子線程讀取本地變量:Stu(name=aaa, age=55)
主線程讀取本地變量:Stu(name=aaa, age=1)
主線程讀取本地變量:Stu(name=aaa, age=99)
子線程讀取本地變量:Stu(name=aaa, age=55)
這樣,主子線程的對象纔算真正的複製過去,而不是僅僅複製了一個引用。如此就不存在線程安全的問題了