細說ThreadLocal(一)


前言

java虛擬機在執行Java程序的過程中會把它所管理的內存划分為若干個不同的數據區域。如下圖所示:

其中堆是占虛擬機中內存最大的,堆被所有線程所共享,其最主要的便是存放實例對象。也因為堆內存是共享的,因此在多線程操作的條件下,多線程中堆內存中的數據十分容易發生線程安全的問題。因此為了保證多個線程對變量的安全訪問,我們可以將變量放到ThreadLocal對象中,變量在每個線程中都有獨立值,線程只能操作自己的變量,訪問不到其他線程中的變量。

ThreadLocal

ThreadLocal顧名思義便是線程本地變量的意思,在JAVA程序中每new一個ThreadLocal對象實例時,每個線程就會有一個隸屬於自己的變量,一個專屬於線程的變量,也因此該變量不會被其他的線程訪問到,以此來規避了線程安全的問題。

那么ThreadLocal如何使得每個線程擁有自己獨有的本地值呢?

在JDK8的版本中,每一個線程都有一個屬於自己的ThreadLocalMap,ThreadLocalMap隨着Thread的創建而存在,隨着Thread的實例銷毀而銷毀。

        //ThreadLocalMap其中一個構造函數
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

而ThreadLocalMap即是保存本地變量的關鍵之一,它首先以ThreadLocal實例和變量值作為Entry對象的構造參數來構造Entry對象,后以ThreadLocal實例進行散列計算hash,在散列函數計算后,每個ThreadLocal會均勻地、獨立地被分布在Entry數組中,也就是會得到自己在Entry數組中的索引值,然后用此索引將構造出來的Entry對象放入到Entry數組中。也由於每個線程都有自己的ThreadLocalMap,因此變量值是存放在專屬於自己線程的ThreadLocalMap中,這個ThreadLocalMap其他線程獲取不到,所以每個線程都有專屬於自己的變量值,在操作的時候也是對自己專屬的變量值進行操作。

從上圖我們也可以知道ThreadLocalMap其實是由ThreadLocal來進行管理的,兩者的關系密不可分。

我們在平時的操作中,大多是操作ThreadLocal的get(),set()方法,似乎ThreadLocalMap接觸的較少,但在接下來深入ThreadLocal的時候,我們會發現ThreadLocal這個類其實是基於ThreadLocalMap來完成的。

ThreadLocal的get方法

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

上面的代碼塊展示的是ThreadLocal中get方法的源碼,通過源碼我們可以了解到get方法有以下步驟:

  1. 首先它會獲取當前占有CPU時間片的線程的實例,然后通過當前線程的實例調用getMap()方法來獲取當前線程的ThreadLocalMap。
 //getMap()方法
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
  1. 如果ThreadLocalMap不為空的話,就以自身ThreadLocal實例作為參數調用ThreadLocalMap的getEntry()方法來獲取到Entry對象,如果Entry對象不為空,則獲取Entry的value屬性值返回。
  2. 如果map為空的話或者此ThreadLocal實例計算出的hash值最為Entry數組的索引在Entry數組中並未存在Entry對象,證明當前線程並未初始化ThreadLocalMap,調用setInitialValue方法后返回。

ThreadLocal的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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

我們來探尋get()方法中調用的setInitialValue方法,在以下代碼中我們可以知道:

  1. 首先一上來就會調用一個鈎子函數initialValue()來給value變量賦值,但是我們進入initialValue()方法卻發現這個方法的返回值是null,如果需要繼承ThreadLocal來重寫這個方法就太麻煩,JDK已經為大家定義了ThreadLocal的內部SuppliedThreadLocal靜態子類,並且提供了ThreadLocal.withInitial()靜態工廠方法,我們只需要在定義一個ThreadLocal類型變量時,使用這個方法。

initialValue鈎子函數只會調用一次,且只在不使用ThreadLocal.set()方法去設置值就使用ThreadLocal.get()方法去獲取值的時候,會執行。

      //鈎子函數
      protected T initialValue() {
                return null;
          }

      //靜態工廠方法
      public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
          return new SuppliedThreadLocal<>(supplier);
      }
  1. get方法的步驟一致,也是獲取當前的線程后,獲取當前線程的ThreadLocalMap,如果沒有的話則創建為ThreadLocalMap創建一個map,這是因為一開始Thread下面的ThreadLocalMap初始值為空,所以有create這個步驟。在creatMap的方法中,我們可以看到了新建了一個ThreadLocalMap類,並以當前的ThreadLocal實例對象和initialValue產生的值作為構造參數,以此生成Entry對象保存在Entry數組中。
       void createMap(Thread t, T firstValue) {
          t.threadLocals = new ThreadLocalMap(this, firstValue);
       }
  1. 最后方法返回。

ThreadLocal的set方法

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

以上是ThreadLocal的set方法的源碼,它相對於get方法較簡單,也是獲取當前線程並獲取當前線程的ThreadLocalMap,如果有的話調用ThreadLocalMap的set方法將值設置進去,如果ThreadLocal為空,則使用createMap方法去創建一個ThreadLocalMap。

ThreadLocal的remove方法

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }

ThreadLocal的remove方法便更簡單了,它僅判斷獲取到的當前線程的ThreadLocalMap不為空,則調用了ThreadLocalMap的remove方法去刪除值。

后續

從以上的ThreadLocal函數,我們可以看到,許多重要的方法都是依靠着ThreadLocalMap及其api去完成對ThreadLocal方法的實現的,不難看出理解ThreadLocalMap其實相較於理解ThreadLocal是比較重要的,而ThreadLocalMap內部對Entry這個子類的實現,更是考慮到了ThreadLocal的內存泄漏,因此使用了WeakReference弱引用去關聯ThreadLocal實例,防止強引用導致的內存泄露的問題。

對ThreadLocalMap我會另開一個隨筆去寫,請多多擔待。

結尾

本文參考

[1] 周志明.深入理解Java虛擬機:JVM高級特性與最佳實踐.-2版.北京:機械工業出版社,2013.6
[2] 尼恩.Java高並發編程.卷2,多線程、鎖、JMM、JUC、高並發設計模式.北京:機械工業出版社,2021.5


免責聲明!

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



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