ThreadLocal原理分析

接下來筆者的文章方向偏向于 Android & Java 面試相關知識點系統(tǒng)性的總結,歡迎關注。

ThreadLocal類是java.lang包下的一個類,用于線程內(nèi)部的數(shù)據(jù)存儲,通過它可以在指定的線程中存儲數(shù)據(jù),本文針對該類進行原理分析。

通過思維導圖對其進行簡單的總結:

image

一.ThreadLocal源碼分析

ThreadLocal類最重要的幾個方法如下:

  • get():T 獲取當前線程下存儲的變量副本
  • set(T):void 存儲該線程下的某個變量副本
  • remove():void 移除該線程下的某個變量副本

1.get()方法分析

ThreadLocal類比較簡單,其最重要的就是get()set()方法,顧名思義,起作用就是取值和設置值:

// 獲取當前線程中的變量副本
public T get() {
    // 獲取當前線程
    Thread t = Thread.currentThread();
    // 獲取線程中的ThreadLocalMap對象
    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;
        }
    }
    // 若沒有該變量副本,返回setInitialValue()
    return setInitialValue();
}

這里先將ThreadLocalMap暫時理解為一個Map結構的容器,內(nèi)部存儲著該線程作用域下的的所有變量副本,我們從ThreadLocal類中取值的時候,實際上是從ThreadLocalMap中取值。

如果Map中沒有該變量的副本,會從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;
}

可以看到,setInitialValue()中也非常的簡單,依然是從當前線程中獲取到ThreadLocalMap,略微不同的是,setInitialValue()會對變量進行初始化,存入ThreadLocalMap中并返回。

這個初始化的方法的執(zhí)行,需要開發(fā)者自己重寫initialValue()方法,否則返回值依然為null。

public class ThreadLocal<T> {
    // ...
    protected T initialValue() {
       return null;
    }
}    

2.set()方法分析

setInitialValue()方法類似,set()方法也非常簡單:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // map不為空,直接將ThreadLocal對象作為key
        // 變量本身的值為value,存入map
        map.set(this, value);
    else
        // 否則,創(chuàng)建ThreadLocalMap
        createMap(t, value);
}

可以看到,這個方法的作用就是將變量副本作為value存入Map,需要注意的是,key并非是我們下意識認為的Thread對象,而是ThreadLocal本身(ThreadValue本身是一對一的,我們更容易將其映射為key-value的關系)。

3.remove()方法分析

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

對于變量副本的移除,也是通過map進行處理的,和set()get()相同,Entry的鍵值對中,ThreadLocal本身作為key,對變量副本進行檢索。

4.小結

可以看出,ThreadLocal本身內(nèi)部的邏輯都是圍繞著ThreadLocalMap在運作,其本身更像是一個空殼,僅作為API供開發(fā)者調用,內(nèi)部邏輯都委托給了ThreadLocalMap

接下來我們來探究一下ThreadLocalMapThread以及ThreadLocal之間的關系。

二、ThreadLocalMap分析

ThreadLocalMap內(nèi)部代碼和算法相對復雜,個人亦是一知半解,因此就不逐行代碼進行分析,僅系統(tǒng)性進行概述。

首先來看一下ThreadLocalMap的定義:

public class ThreadLocal<T> {

    // ThreadLocalMap是ThreadLocal的內(nèi)部類
    static class ThreadLocalMap {

      // Entry類,內(nèi)部key對應的是ThreadLocal的弱引用
      static class Entry extends WeakReference<ThreadLocal<?>> {
          // 變量的副本,強引用
          Object value;

          Entry(ThreadLocal<?> k, Object v) {
              super(k);
              value = v;
          }
      }
    }
}

ThreadLocal中的嵌套內(nèi)部類ThreadLocalMap本質上是一個map,依然是key-value的形式,其中有一個內(nèi)部類Entry,其中key可以看做是ThreadLocal實例的弱引用。

和最初的設想不同的是,ThreadLocalMapkey并非是線程的實例Thread,而是ThreadLocal,那么ThreadLocalMap是如何保證同一個Thread中,ThreadLocal的指定變量唯一呢?

// 1.ThreadLocal的set()方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // ...
}

// 2.getMap()實際上是從Thread中獲取threadLocals成員
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

public class Thread implements Runnable {
    // 3.每個Thread實例都持有一個ThreadLocalMap的屬性
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

Thread本身持有ThreadLocal.ThreadLocalMap的屬性,每個線程在向ThreadLocalsetValue的時候,其實都是向自己的ThreadLocalMap成員中加入數(shù)據(jù);get()同理。

三、內(nèi)存泄漏的風險?

在上一小節(jié)中,我們看到ThreadLocalMap中的Entry中,其ThreadLocal作為key,是作為弱引用進行存儲的。

ThreadLocal不再被作為強引用持有時,會被GC回收,這時ThreadLocalMap對應的ThreadLocal就變成了null。而根據(jù)文檔所敘述的,當key == null時,這時就可以默認該鍵不再被引用,該Entry就可以被直接清除,該清除行為會在Entry本身的set()/get()/remove()中被調用,這樣就能 一定情況下避免內(nèi)存泄漏。

這時就有一個問題出現(xiàn)了,作為keyThreadLocal變成了null,那么作為value的變量可是強引用呀,這不就導致內(nèi)存泄漏了嗎?

其實一般情況下也不會,因為即使再不濟,線程在執(zhí)行結束時,自然也會消除其對value的引用,使得Value能夠被GC回收。

當然,在某種情況下(比如使用了 線程池),線程再次被使用,Value這時依然可以被獲取到,自然也就發(fā)生了內(nèi)存泄漏,因此此時,我們還是需要通過手動將value的值設置為null(即調用ThreadLocal.remove()方法)以規(guī)避內(nèi)存泄漏的風險。

參考&感謝


關于我

Hello,我是卻把清梅嗅,如果您覺得文章對您有價值,歡迎 ??,也歡迎關注我的博客或者Github

如果您覺得文章還差了那么點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 介紹 ThreadLocal類支持泛型,ThreadLocal<T>,T代表線程本地變量類型,在多線程中,每個線程...
    gczxbb閱讀 463評論 0 0
  • ThreadLocal提供了線程本地變量,它可以保證訪問到的變量屬于當前線程,每個線程都保存有一個變量副本,每個線...
    FX_SKY閱讀 15,628評論 0 3
  • 本篇文章的主要內(nèi)容如下: 1、Java中的ThreadLocal2、 Android中的ThreadLocal3、...
    Sophia_dd35閱讀 681評論 0 5
  • 原文鏈接:Java進階(七)正確理解Thread Local的原理與適用場景 - 郭俊Jason - 博客園 Th...
    Walter_Hu閱讀 877評論 0 1
  • 下面我就以面試問答的形式學習我們的——ThreadLocal類(源碼分析基于JDK8) 問答內(nèi)容 1、問:Thre...
    Sophia_dd35閱讀 2,156評論 1 36

友情鏈接更多精彩內(nèi)容