詳解ThreadLocal

前言

本篇文章我來討論一下什麼是ThreadLocal以及它的實現原理。其底層數據結構有點相似HashMap,因此對HashMap不熟悉的朋友能夠先去看一看我前面介紹HashMap的那篇文章。java

本文如如有不對或不實之處,也歡迎各位讀者朋友評論指正,歡迎探討交流。
數組

1、什麼是ThreadLocal

我總結以後以爲能夠這樣理解:

ThreadLocal提供了線程的局部變量,每一個線程均可以經過set()和get()來對這個局部變量進行操做,但不會和其餘線程的局部變量進行衝突,實現了線程的數據隔離。安全

簡而言之:ThreadLocal中填充的變量屬於當前線程,該變量對其餘線程而言是不可見的。

2、ThreadLocal實現的原理

咱們來翻一翻ThreadLocal的源碼實現,這是學習理解一個工具類最簡單有效的方法。

ThreadLocal,鏈接ThreadLocalMap和Thread。來處理Thread的TheadLocalMap屬性,包括init初始化屬性賦值、get對應的變量,set設置變量等。經過當前線程,獲取線程上的ThreadLocalMap屬性,對數據進行get、set等操做。

ThreadLocalMap,用來存儲數據,採用相似hashmap機制, 存儲了以threadLocal爲key,須要隔離的數據爲value的Entry鍵值對數組結構。

1. ThreadLocal、ThreadLocal、Thread之間的關係

ThreadLocalMap是ThreadLocal內部類,由ThreadLocal建立,Thread有ThreadLocal.ThreadLocalMap類型的屬性。

2. ThreadLoalMap

從名字上看,能夠猜到它也是一個相似HashMap的數據結構,可是在ThreadLocal中,並沒實現Map接口。

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    //...
}複製代碼
經過上面咱們能夠發現的是ThreadLocalMap是ThreadLocal的一個內部類。用Entry類來進行存儲

咱們的值都是存儲到這個Map上的,key是當前ThreadLocal對象!數據結構

若是該Map不存在,則初始化一個:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}複製代碼
若是該Map存在,則從Thread中獲取!

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
複製代碼
Thread維護了ThreadLocalMap變量

/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null複製代碼
從上面又能夠看出,ThreadLocalMap是在ThreadLocal中使用內部類來編寫的,但對象的引用是在Thread中。因而咱們能夠總結出:Thread爲每一個線程維護了ThreadLocalMap這麼一個Map,而ThreadLocalMap的key是ThreadLocal對象自己,value則是要存儲的對象。

在ThreadLoalMap中,也是初始化一個大小16的Entry數組,Entry對象用來保存每個key-value鍵值對,只不過這裏的key永遠都是ThreadLocal對象,經過ThreadLocal對象的set方法,結果把ThreadLocal對象本身當作key,放進了ThreadLoalMap中。併發


這裏須要注意的是,ThreadLoalMap和HashMap很大的區別是,Entry中沒有next字段,因此就不存在鏈表的狀況了。若是出現hash衝突怎麼辦?先來看看set方法。

3. set() 方法

首先,咱們來看一下ThreadLocal的set()方法,由於咱們通常使用都是new完對象,就往裏邊set對象了

public void set(T value) {
    // 獲得當前線程對象
    Thread t = Thread.currentThread();
    // 這裏獲取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 若是map存在,則將當前線程對象t做爲key,要存儲的對象
    //做爲value存到map裏面去
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

private void set(ThreadLocal key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    //根據hash值計算存放下標i
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
複製代碼
在插入過程當中,根據ThreadLocal對象的hash值,定位到table中的位置i,過程以下:
一、若是當前位置是空的,那麼正好,就初始化一個Entry對象放在位置i上;
二、若是位置 i 已經有Entry對象了,若是這個Entry對象的key正好是即將設置的key,那麼從新設置Entry中的value;
三、很不巧,位置i的Entry對象,和即將設置的key不要緊,那麼只能找下一個空位置;

這樣的話,在get的時候,也會根據ThreadLocal對象的hash值,定位到table中的位置,而後判斷該位置Entry對象中的key是否和get的key一致,若是不一致,就判斷下一個位置,能夠發現,set和get若是衝突嚴重的話,效率很低。工具

4. get() 方法

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();
}

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}
//查找對應key的 Entry
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
} 複製代碼

3、ThreadLocal原理總結

  1. 每一個Thread維護着一個ThreadLocalMap的引用
  2. ThreadLocalMap是ThreadLocal的內部類,用Entry來進行存儲
  3. 調用ThreadLocal的set()方法時,實際上就是往ThreadLocalMap設置值,key是ThreadLocal對象,值是傳遞進來的對象
  4. 調用ThreadLocal的get()方法時,實際上就是在ThreadLocalMap獲取值,key是ThreadLocal對象
  5. ThreadLocal自己並不存儲值,它只是做爲一個key來讓線程從ThreadLocalMap獲取value。
正是上面的這些原理,因此ThreadLocal可以實現「數據隔離」,獲取當前線程的局部變量值,不受其餘線程影響。

4、總結

ThreadLocal設計的目的就是爲了可以在當前線程中有屬於本身的變量,實現不一樣線程間的數據隔離,線程間的數據互不干擾。ThreadLocal是解決線程安全問題一個很好的思路,它經過爲每一個線程提供一個獨立的變量副本解決了變量併發訪問的衝突問題。
相關文章
相關標籤/搜索