首先再討論題主的這個觀點之前我們要明確一下ThreadLocal的用途是什么?
ThreadLocal並不是用來解決共享對象的多線程訪問問題。
看了許多有關ThreadLocal的博客,看完之后會給人一種錯覺,ThreadLocal就是用於在多線程情況下防止共享對象的線程安全問題,使用ThreadLocal之后,ThreadLocal的對象就不會有線程安全問題,但是一定是這樣么,看如下代碼
-
public class test {
-
public static void main(String[] args) throws InterruptedException {
-
new A().start();
-
new A().start();
-
new A().start();
-
new A().start();
-
}
-
-
static class A extends Thread {
-
static List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
-
static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
-
-
protected List<Integer> initialValue() {
-
return list;
-
}
-
};
-
-
-
public void run() {
-
List<Integer> threadList = threadLocal.get();
-
threadList.add(threadList.size());
-
System.out.println(threadList.toString());
-
}
-
-
}
-
}
該代碼很簡單,就是在多線程的情況下輸出ThreadLocal的list集合狀態,如果此時線程安全的話,輸出的4個語句應該是完全一樣的輸出結果如下:
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5, 6]
[1, 2, 3, 4, 5, 5, 6, 7]
[1, 2, 3, 4, 5, 5, 6, 7, 8]
很明顯可以看到,線程是不安全的,從結果也能看出來4個線程中ThreadLocal中的List是同一個對象,被四個線程所共享。
接下來我們分析一下原因的發生原因,我們去看ThreadLocal的get()方法
-
public T get() {
-
Thread t = Thread.currentThread();
-
ThreadLocalMap map = getMap(t);
-
if (map != null) {
-
ThreadLocalMap.Entry e = map.getEntry( this);
-
if (e != null) {
-
-
T result = (T)e.value;
-
return result;
-
}
-
}
-
return setInitialValue();
-
}
如果一個線程第一次調用threadLocal.get()方法時,我們通過調試可以發現此時拿到的map是null,會調用setInitialValue(),繼續看該方法
-
private T setInitialValue() {
-
T value = initialValue();
-
Thread t = Thread.currentThread();
-
ThreadLocalMap map = getMap(t);
-
if (map != null)
-
map.set( this, value);
-
else
-
createMap(t, value);
-
return value;
-
}
可以看到這個方法里面有一個value,而這個value就是ThreadLocal中即將要保存的只對線程所見的"副本",而這個value使用過initialValue()方法得到的,這個方法如果你沒有重寫的話默認返回時一個null,而在我們的例子當中他返回的是我們定義的靜態變量list,而這個list是一個單例的(只會被類第一次訪問時進行初始化),也就是說我們的例子中每個線程的ThreadLocal里面get的都是同一個list,所以就造成了線程安全的問題.
接下來改善一下我們的代碼
-
static class A extends Thread {
-
static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
-
-
protected List<Integer> initialValue() {
-
return new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
-
}
-
};
-
-
-
public void run() {
-
List<Integer> threadList = threadLocal.get();
-
threadList.add(threadList.size());
-
System.out.println(threadList.toString());
-
}
-
-
}
運行結果
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5]
接下來回到主題,ThreadLocal的作用是什么:
ThreadLocal使得各線程能夠保持各自獨立的一個對象,而實現原理其實是通過,每個線程都會重新創建一個對象,不是什么對象的拷貝或副本,而線程是否安全取決於你如何去創建這個對象。
然后總結一下:
1.ThreadLocal作用不是為了解決共享對象的多線程安全問題,而是為了避免通多參數傳遞的方式去拿到一個對象,網上有些例子就一開始拿線程安全舉例子,然后拋磚引玉出ThreadLocal,會把人帶偏。。。博主就是一個。
2.ThreadLocal中保存的不是什么對象的副本,里面沒有需要保存的對象做任何復制,拷貝操作,這個對象完全取決於你的iniialtValue方法中如何去創建,所以這里需要考慮使用ThreadLocal的性能問題,是否會大量創建一個對象。
