ThreadLocal字面意思是線程局部變量,它爲每個線程提供了獨立的互不干擾的局部變量。html
下面以一個簡單的例子來簡單介紹下ThreadLocal的使用:java
public class ThreadLocalDemo2 {
public static class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 1;
}
};
@Override
public void run() {
threadLocal.set((int) (Math.random() * 100D));
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable, "A");
Thread t2 = new Thread(myRunnable, "B");
t1.start();
t2.start();
}
/**
B:48
A:32
即:線程A與線程B中ThreadLocal保存的整型變量是各自獨立的,互不相干,只要在每一個線程內部使用set方法賦值,
而後在線程內部使用get就能取到對應的值。
*/
}
複製代碼
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
public static native Thread currentThread();
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
複製代碼
注意:web
ThreadLocal所存儲的變量的實際值是經過ThreadLocalMap結構存在Thread類的成員變量上的,也就是說每個Java線程,Thread類的對象實例,都有一個本身的ThreadLocalMap。數組
ThreadLocalMap是ThreadLocal.java中的一個靜態內部類,它是一個爲了維護線程局部變量(ThreadLocal)定製化的哈希表。瀏覽器
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;
}
}
...
private Entry[] table;
...
}
複製代碼
這個ThreadLocalMap的Key爲泛型類ThreadLocal的實例,Value爲要存儲的ThreadLocal變量T。tomcat
實際ThreadLocal變量T與ThreadLocal的實例一塊兒做爲一個Entry,存儲在table裏面。bash
注意到這裏的ThreadLocalMap.Entry是繼承WeakReference使得做爲Key ThreadLocal的實例爲一個弱引用。那麼,在ThreadLocal的實例僅存在弱引用的且被GC線程掃描到的時候,就會GC回收掉threadLocal實例的內存。這個時候,對應的Key值就爲null了。服務器
這裏設計爲Map是因爲一個線程可能有多個線程局部變量即多個ThreadLocal的對象實例。dom
上面說到ThreadLocalMap,是一個定製化的哈希表。既然是哈希表就須要解決哈希衝突的問題。對於java.util.HashMap,解決衝突的方式是拉鍊法。ide
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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(); } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } 複製代碼
而ThreadLocalMap解決衝突的方式是開放定址法 。
以set操做爲例,簡單的說就是經過Key作一次Hash以後,若是發現哈希結果對應位置的key和當前要set的key不一致,就日後面找,直到找到一個空的位置。
那麼,可不可能找不到呢?
答案是不可能的。
很差的反作用 因爲使用了開放定址法,致使ThreadLocalMap的set,get,remove操做都不能在一次哈希尋址肯定找到正確的位置。須要再花費O(n)的時間進行二次尋址去找到空位置或者是能獲取、刪除的位置。
好的反作用 JDK源碼做者經過另外一種方式利用了開放定址法帶來的二次尋址的循環。在set和get方法的二次尋址的循環過程當中,若是發現了stale entry(即key值爲空,可是Entry值非空,這裏也能夠理解爲value值非空)的位置,就會進行清理。
ThreadLocalMap -> set -> replaceStaleEntry -> expungeStaleEntry
ThreadLocalMap -> get -> getEntry -> getEntryAfterMiss -> expungeStaleEntry
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
...
}
複製代碼
注意:這種方式並不能保證,每次ThreadLocalMap.set 或者 get操做都能清除掉全部的key被回收的entry節點。舉一個極端的反例,ThreadLocalMap有key爲null的entry,可是get操做的第一次hash就直接找到了正確的位置,並無進行二次尋找。那麼,此時就沒法進行清除。
上面提到,ThreadLocalMap::Entry::ThreadLocal是一個弱引用。那麼,爲何要用WeakReference呢?
這裏,咱們反向思考下,若是不使用弱引用,而使用強引用。那麼,在線程的整個生命週期內,全部定義的ThreadLocal變量都一直存在,即便是用戶已經再也不使用ThreadLocal變量了,這是由於下面2條的引用關係鏈一直存在:
ThreadLocalRef->Thread->ThreadLocal->ThreadLocalMap->Entry->key
ThreadLocalRef->Thread->ThreadLocal->ThreadLocalMap->Entry->value
那麼,若是用戶不進行手動的ThreadLocalMap::remove,所佔用的空間就一直釋放不掉。
綜上,我理解的使用ThreadLocalMap->Entry->key(即ThreadLocal)使用弱引用的緣由是爲了在用戶沒有進行手動的ThreadLocalMap::remove狀況下,也能讓系統有方法在set,get的時候進行部分的資源清理。雖然,JVM只清理了key,可是後續JDK源碼設計提供了清理value以及整個entry的機制(將value和entry在ThreadLocalMap中的強引用給消除掉)。可是,這機制不必定能用上。
So,每次肯定ThreadLocal再也不使用後,都要手動調用它的remove()方法進行數據清除。
否則,就可能會出現內存泄露。
在ThreadLocal變量僅持有弱引用的時候,若是經歷了GC就會被清除掉內存。而後,ThreadLocalMap的ThreadLocal key就變成null了。可是,對應的value因爲上面所寫的強引用關係鏈還一直存在,就沒發被回收。因而,就發生了value值沒發被獲取和使用,可是又沒法被回收的狀況,即內存泄漏。
舉幾個例子說明一下:
從這個簡單的訪問過程咱們看到正好這個 Session 是在處理一個用戶會話過程當中產生並使用的,若是單純的理解一個用戶的一次會話對應服務端一個獨立的處理線程,那用 ThreadLocal 在存儲 Session ,簡直是再合適不過了。可是例如 tomcat 這類的服務器軟件都是採用了線程池技術的,並非嚴格意義上的一個會話對應一個線程。並非說這種狀況就不適合 ThreadLocal 了,而是要在每次請求進來時先清理掉以前的 Session ,通常能夠用攔截器、過濾器來實現。
最後,以爲寫的不錯的同窗麻煩點個贊,支持一下唄^_^~