正確理解ThreadLocal:ThreadLocal中的值並不一定是完全隔離的


首先再討論題主的這個觀點之前我們要明確一下ThreadLocal的用途是什么?

ThreadLocal並不是用來解決共享對象的多線程訪問問題。

看了許多有關ThreadLocal的博客,看完之后會給人一種錯覺,ThreadLocal就是用於在多線程情況下防止共享對象的線程安全問題,使用ThreadLocal之后,ThreadLocal的對象就不會有線程安全問題,但是一定是這樣么,看如下代碼

  1.  
    public class test {
  2.  
    public static void main(String[] args) throws InterruptedException {
  3.  
    new A().start();
  4.  
    new A().start();
  5.  
    new A().start();
  6.  
    new A().start();
  7.  
    }
  8.  
     
  9.  
    static class A extends Thread {
  10.  
    static List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
  11.  
    static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
  12.  
    @Override
  13.  
    protected List<Integer> initialValue() {
  14.  
    return list;
  15.  
    }
  16.  
    };
  17.  
     
  18.  
    @Override
  19.  
    public void run() {
  20.  
    List<Integer> threadList = threadLocal.get();
  21.  
    threadList.add(threadList.size());
  22.  
    System.out.println(threadList.toString());
  23.  
    }
  24.  
     
  25.  
    }
  26.  
    }

該代碼很簡單,就是在多線程的情況下輸出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()方法

  1.  
    public T get() {
  2.  
    Thread t = Thread.currentThread();
  3.  
    ThreadLocalMap map = getMap(t);
  4.  
    if (map != null) {
  5.  
    ThreadLocalMap.Entry e = map.getEntry( this);
  6.  
    if (e != null) {
  7.  
    @SuppressWarnings("unchecked")
  8.  
    T result = (T)e.value;
  9.  
    return result;
  10.  
    }
  11.  
    }
  12.  
    return setInitialValue();
  13.  
    }

如果一個線程第一次調用threadLocal.get()方法時,我們通過調試可以發現此時拿到的map是null,會調用setInitialValue(),繼續看該方法

  1.  
    private T setInitialValue() {
  2.  
    T value = initialValue();
  3.  
    Thread t = Thread.currentThread();
  4.  
    ThreadLocalMap map = getMap(t);
  5.  
    if (map != null)
  6.  
    map.set( this, value);
  7.  
    else
  8.  
    createMap(t, value);
  9.  
    return value;
  10.  
    }

可以看到這個方法里面有一個value,而這個value就是ThreadLocal中即將要保存的只對線程所見的"副本",而這個value使用過initialValue()方法得到的,這個方法如果你沒有重寫的話默認返回時一個null,而在我們的例子當中他返回的是我們定義的靜態變量list,而這個list是一個單例的(只會被類第一次訪問時進行初始化),也就是說我們的例子中每個線程的ThreadLocal里面get的都是同一個list,所以就造成了線程安全的問題.

接下來改善一下我們的代碼

  1.  
    static class A extends Thread {
  2.  
    static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
  3.  
    @Override
  4.  
    protected List<Integer> initialValue() {
  5.  
    return new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
  6.  
    }
  7.  
    };
  8.  
     
  9.  
    @Override
  10.  
    public void run() {
  11.  
    List<Integer> threadList = threadLocal.get();
  12.  
    threadList.add(threadList.size());
  13.  
    System.out.println(threadList.toString());
  14.  
    }
  15.  
     
  16.  
    }

運行結果

[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的性能問題,是否會大量創建一個對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM