ThreadLocal 是一個線程安全副本,用於儲存僅容許當前線程能訪問/修改的值,不知從什麼時候起看到了」線程安全「這種字眼就會不自覺想到性能問題,可是ThreadLocal是實現線程安全的另一種方案"空間換時間"。數組
先看2個小Demo安全
使用ThreadLocalide
public class ThreadLoacalTest{ // 定義線程安全副本 private final static ThreadLocal<Integer> THREAD_NUMBER = new ThreadLocal<Integer>(); // 繼承Thread重寫run() static class ThreadTest extends Thread{ @Override public void run() { for (int i=0; i < 3 ;i++){ // 若是THREAD_NUMBER 爲null,賦值0,不然+1 THREAD_NUMBER.set(THREAD_NUMBER.get() == null ? 0 : THREAD_NUMBER.get() + 1); // 打印信息 System.out.println(Thread.currentThread().getName() + ":" + THREAD_NUMBER.get()); } THREAD_NUMBER.remove(); } } public static void main(String[] args) { ThreadTest t1 = new ThreadTest(); ThreadTest t2 = new ThreadTest(); ThreadTest t3 = new ThreadTest(); t1.start(); t2.start(); t3.start(); } }
不使用ThreadLocal性能
public class Test { // 定義普一般量 public static Integer THREAD_NUMBER = 0; // 繼承Thread重寫run() static class ThreadTest extends Thread{ @Override public void run() { for (int i=0; i < 3 ;i++){ // 打印信息 System.out.println(Thread.currentThread().getName() + ":" + THREAD_NUMBER++); } } } public static void main(String[] args) { ThreadTest t1 = new ThreadTest(); ThreadTest t2 = new ThreadTest(); ThreadTest t3 = new ThreadTest(); t1.start(); t2.start(); t3.start(); } }
從上面的打印結果能夠看出,使用了ThreadLocal的常量不會與其餘線程共享,而沒有使用ThreadLocal的常量是會與其餘線程共享學習
接下來看源碼this
從set()開始吧spa
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
1.先獲取當前線程對象線程
2.經過當前線程對象獲取一個ThreadLocalMap 實例(如下簡稱map)3d
3.若是map不爲null 則直接向map插入數據對象
4.若是爲null則調用createMap()建立一個map而且向map插入數據
該map以當前對象做爲key,這樣咱們就只須要關注value而不用維護key
getMap()
能夠看到這個threadLocals屬性是Thread的,可是類是屬於ThreadLocal的一個內部類ThreadLocalMap
由於這個map對象屬於Thread的實例,每一個Thread都是特有的map,因此能提供整個線程使用且不與其餘線程共享
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocal.ThreadLocalMap threadLocals = null;
createMap()
2個參數,一個是key(當前ThreadLocal實例),一個是value(咱們維護的對象)
下面代碼能夠看出值都是保存在一個Entry對象的數組裏面,以及一些初始化的工做
Entry又是ThreadLocal.ThreadLocalMap 的一個內部類 ThreadLocal.ThreadLocalMap.Entry
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.set()
在這裏向Entry[]添加元素,計算出下標,若是該下標位置沒有元素,則直接插入元素。若是有元素,則遍歷數組直到沒有元素的下標位置才停下進行存儲,若是遇到相同key則更新元素而且遇到key爲null的Entry時,會刪除元素。存儲完以後判斷當前數組容量是否須要擴容,若是進行了擴容,全部元素都會從新存儲一遍
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
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();
}
接下來看看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(); }
咱們獲取ThreadLocal維護的變量都是直接經過ThreadLocal的get()獲取
1.獲取當前線程對象
2.經過當前線程對象獲取map
3.經過當前對象向map獲取值(Entry對象),若是map與Entry對象都不爲null則直接返回存儲的值
4.若是值爲null,則調用setInitialValue()初始化鍵值對並返回null
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() 先經過initialValue()初始化值,而後保存到map而且返回初始化的值
(判斷是否須要建立map這個步驟實在不想寫了,ThreadLocal裏面太多這樣的操做了)
protected T initialValue() {
return null;
}
ThreadLocalMap.getEntry()
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對應值儲存的下標,當元素不爲null且key相等,則返回對應的值,若是下標與key不對應,則遍歷數組查詢,整個數組都不存在該key,則返回null,若是遇到key爲null的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; }
以上源碼學習總結:
1.ThreadLocal原理是該類主要操做一個Thread類的 ThreadLocalMap threadLocals屬性,該屬性是屬於每一個Thread實例特有的,因此能提供整個線程使用且不與其餘線程共享
2.ThreadLocalMap 的底層實際上是一個素組,當計算到的存儲下標已存在元素,則循環判斷是否爲null(可否存儲),若是存在相同key則更新元素
3.get()、set()操做的時候若是碰到了key爲null的狀況都會刪除key爲null的 Entry 對象
都說ThreadLocal是以"空間換時間",以上代碼也證明了確實如此。可是有個問題,就是空間用得越多,就越容易OOM。因此Entry繼承了WeakReference弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
這裏的key是弱引用,就是key指向的對象(ThreadLocal)若是沒有其餘強引用的狀況下,下次GC的時候就會被回收,這保證了必定的垃圾回收效率,可是若是存在其餘強引用狀況下,GC並不會回收該對象,因此咱們使用ThreadLocal的時候須要注意有沒有被強引用