參考:http://www.iteye.com/topic/103804
http://www.iteye.com/topic/777716java
爲了解釋ThreadLocal類的工做原理,必須同時介紹與其工做甚密的其餘幾個類多線程
首先,在Thread類中有一行:併發
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
其中ThreadLocalMap類的定義是在ThreadLocal類中,真正的引用倒是在Thread類中。同時,ThreadLocalMap中用於存儲數據的entry定義:函數
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
從中咱們能夠發現這個Map的key是ThreadLocal類的實例對象,value爲用戶的值,並非網上大多數的例子key是線程的名字或者標識。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(); }
其中的getMap方法:this
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
給當前Thread類對象初始化ThreadlocalMap屬性:線程
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
到這裏,咱們就能夠理解ThreadLocal到底是如何工做的了設計
public class Son implements Cloneable{ public static void main(String[] args){ Son p=new Son(); System.out.println(p); Thread t = new Thread(new Runnable(){ public void run(){ ThreadLocal<Son> threadLocal = new ThreadLocal<>(); System.out.println(threadLocal); threadLocal.set(p); System.out.println(threadLocal.get()); threadLocal.remove(); try { threadLocal.set((Son) p.clone()); System.out.println(threadLocal.get()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } System.out.println(threadLocal); }}); t.start(); } }
輸出:code
Son@7852e922 java.lang.ThreadLocal@3ffc8195 Son@7852e922 Son@313b781a java.lang.ThreadLocal@3ffc8195
也就是若是把一個共享的對象直接保存到ThreadLocal中,那麼多個線程的ThreadLocal.get()取得的仍是這個共享對象自己,仍是有併發訪問問題。 因此要在保存到ThreadLocal以前,經過克隆或者new來建立新的對象,而後再進行保存。
ThreadLocal的做用是提供線程內的局部變量,這種變量在線程的生命週期內起做用。做用:提供一個線程內公共變量(好比本次請求的用戶信息),減小同一個線程內多個函數或者組件之間一些公共變量的傳遞的複雜度,或者爲線程提供一個私有的變量副本,這樣每個線程均可以隨意修改本身的變量副本,而不會對其餘線程產生影響。對象
如何實現一個線程多個ThreadLocal對象,每個ThreadLocal對象是如何區分的呢?
查看源碼,能夠看到:
private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
對於每個ThreadLocal對象,都有一個final修飾的int型的threadLocalHashCode不可變屬性,對於基本數據類型,能夠認爲它在初始化後就不能夠進行修改,因此能夠惟一肯定一個ThreadLocal對象。
可是如何保證兩個同時實例化的ThreadLocal對象有不一樣的threadLocalHashCode屬性:在ThreadLocal類中,還包含了一個static修飾的AtomicInteger([əˈtɒmɪk]提供原子操做的Integer類)成員變量(即類變量)和一個static final修飾的常量(做爲兩個相鄰nextHashCode的差值)。因爲nextHashCode是類變量,因此每一次調用ThreadLocal類均可以保證nextHashCode被更新到新的值,而且下一次調用ThreadLocal類這個被更新的值仍然可用,同時AtomicInteger保證了nextHashCode自增的原子性。
爲何不直接用線程id來做爲ThreadLocalMap的key?
這一點很容易理解,由於直接用線程id來做爲ThreadLocalMap的key,沒法區分放入ThreadLocalMap中的多個value。好比咱們放入了兩個字符串,你如何知道我要取出來的是哪個字符串呢?
而使用ThreadLocal做爲key就不同了,因爲每個ThreadLocal對象均可以由threadLocalHashCode屬性惟一區分或者說每個ThreadLocal對象均可以由這個對象的名字惟一區分(下面的例子),因此能夠用不一樣的ThreadLocal做爲key,區分不一樣的value,方便存取。
public class Son implements Cloneable{ public static void main(String[] args){ Thread t = new Thread(new Runnable(){ public void run(){ ThreadLocal<Son> threadLocal1 = new ThreadLocal<>(); threadLocal1.set(new Son()); System.out.println(threadLocal1.get()); ThreadLocal<Son> threadLocal2 = new ThreadLocal<>(); threadLocal2.set(new Son()); System.out.println(threadLocal2.get()); }}); t.start(); } }
ThreadLocal的內存泄露問題
根據上面Entry方法的源碼,咱們知道ThreadLocalMap是使用ThreadLocal的弱引用做爲Key的。下圖是本文介紹到的一些對象之間的引用關係圖,實線表示強引用,虛線表示弱引用:
如上圖,ThreadLocalMap使用ThreadLocal的弱引用做爲key,若是一個ThreadLocal沒有外部強引用引用他,那麼系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry的value,若是當前線程再遲遲不結束的話,這些key爲null的Entry的value就會一直存在一條強引用鏈:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永遠沒法回收,形成內存泄露。
ThreadLocalMap設計時的對上面問題的對策:
ThreadLocalMap的getEntry函數的流程大概爲:
關於ThreadLocalMap內部類的簡單介紹 初始容量16,負載因子2/3,解決衝突的方法是再hash法,也就是:在當前hash的基礎上再自增一個常量。