ThreadLocal原理分析

接下來我的的學習方向偏向於 Android & Java 面試相關知識點系統性的總結,歡迎關注。java

ThreadLocal類是java.lang包下的一個類,用於線程內部的數據存儲,經過它能夠在指定的線程中存儲數據,本文針對該類進行原理分析。android

經過思惟導圖對其進行簡單的總結:git

一.ThreadLocal源碼分析

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

  • 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結構的容器,內部存儲着該線程做用域下的的全部變量副本,咱們從ThreadLocal類中取值的時候,其實是從ThreadLocalMap中取值。算法

若是Map中沒有該變量的副本,會從setInitialValue()中取值:app

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中並返回。源碼分析

這個初始化的方法的執行,須要開發者本身重寫initialValue()方法,不然返回值依然爲null學習

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

2.set()方法分析

setInitialValue()方法相似,set()方法也很是簡單:this

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
        // 不然,建立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自己內部的邏輯都是圍繞着ThreadLocalMap在運做,其自己更像是一個空殼,僅做爲API供開發者調用,內部邏輯都委託給了ThreadLocalMap

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

2、ThreadLocalMap分析

ThreadLocalMap內部代碼和算法相對複雜,我的亦是隻知其一;不知其二,所以就不逐行代碼進行分析,僅系統性進行概述。

首先來看一下ThreadLocalMap的定義:

public class ThreadLocal<T> {

    // ThreadLocalMap是ThreadLocal的內部類
    static class ThreadLocalMap {

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

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

ThreadLocal中的嵌套內部類ThreadLocalMap本質上是一個map,依然是key-value的形式,其中有一個內部類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成員中加入數據;get()同理。

3、內存泄漏的風險?

在上一小節中,咱們看到ThreadLocalMap中的Entry中,其ThreadLocal做爲key,是做爲弱引用進行存儲的。

ThreadLocal再也不被做爲強引用持有時,會被GC回收,這時ThreadLocalMap對應的ThreadLocal就變成了null。而根據文檔所敘述的,當key == null時,這時就能夠默認該鍵再也不被引用,該Entry就能夠被直接清除,該清除行爲會在Entry自己的set()/get()/remove()中被調用,這樣就能 必定狀況下避免內存泄漏

這時就有一個問題出現了,做爲keyThreadLocal變成了null,那麼做爲value的變量但是強引用呀,這不就致使內存泄漏了嗎?

其實通常狀況下也不會,由於即便再不濟,線程在執行結束時,天然也會消除其對value的引用,使得Value可以被GC回收。

固然,在某種狀況下(好比使用了 線程池),線程再次被使用,Value這時依然能夠被獲取到,天然也就發生了內存泄漏,所以此時,咱們仍是須要經過手動將value的值設置爲null(即調用ThreadLocal.remove()方法)以規避內存泄漏的風險。

參考&感謝


關於我

Hello,我是卻把清梅嗅,若是您以爲文章對您有價值,歡迎 ❤️,也歡迎關注個人博客或者Github

若是您以爲文章還差了那麼點東西,也請經過關注督促我寫出更好的文章——萬一哪天我進步了呢?

相關文章
相關標籤/搜索