ThreadLocal<T>類在Spring,Hibernate等框架中起到了很大的做用,對於其工做原理,不少網上的文章分析的不夠完全,甚至有些誤解。 java
首先,爲了解釋ThreadLocal類的工做原理,必須同時介紹與其工做甚密的其餘幾個類(內部類) 多線程
1.ThreadLocalMap 框架
2.Thread ide
可能有人會以爲Thread與ThreadLocal有什麼關係,其實真正的奧祕就在Thread類中的一行: ui
ThreadLocal.ThreadLocalMap threadLocals = null;
其中ThreadLocalMap的定義是在ThreadLocal類中,真正的引用倒是在Thread類中 this
那麼ThreadLocalMap到底是什麼呢? spa
能夠看到這個類應該是一個Map,JDK的解釋是 prototype
接下來的重點是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是線程的名字或者標識 debug
到這裏,咱們就能夠理解ThreadLocal到底是如何工做的了
1.Thread類中有一個成員變量叫作ThreadLocalMap,它是一個Map,他的Key是ThreadLocal類
2.每一個線程擁有本身的申明爲ThreadLocal類型的變量,因此這個類的名字叫'ThreadLocal':線程本身的(變量)
3.此變量生命週期是由該線程決定的,開始於第一次初始(get或者set方法)
4.由ThreadLocal的工做原理決定了:每一個線程獨自擁有一個變量,並不是共享或者拷貝
/** * @author mxdba * */ public class ThreadLocalSample { public static void main(String[] args) { ThreadTest test1 = new ThreadTest(10); ThreadTest test2 = new ThreadTest(20); test1.start(); test2.start(); } } /** * 此線程有兩個ThreadLocal變量,可是因爲ThreadLocal是延遲初始的, * 因此在debug時能夠看到線程名爲「線程20」的線程的ThreadLocalMap中沒有thLcal2這個entry * @author mxdba * */ class ThreadTest extends Thread { public static ThreadLocal<Integer> thLocal = new ThreadLocal<Integer>(); public static ThreadLocal<String> thLocal2 = new ThreadLocal<String>(); public Integer num; public ThreadTest(Integer num) { super("線程" + num); this.num = num; } @Override public void run() { Integer n = thLocal.get(); if(num != 20) { String s = thLocal2.get(); } if(n == null) { thLocal.set(num); } System.out.println(thLocal.get()); } }
接下來分析一下源碼,就更加清楚了
/** * 關鍵方法,返回當前Thread的ThreadLocalMap * [[[每一個Thread返回各自的ThreadLocalMap,因此各個線程中的ThreadLocal均爲獨立的]]] */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
public T get() { Thread t = Thread.currentThread(); /** * 獲得當前線程的ThreadLocalMap */ ThreadLocalMap map = getMap(t); if (map != null) { /** * 在此線程的ThreadLocalMap中查找key爲當前ThreadLocal對象的entry */ ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
private T setInitialValue() { /** * 默認返回null,這個方法爲protected能夠繼承 */ T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else /** * 初次建立 */ createMap(t, value); return value; }
/** * 給當前thread初始ThreadlocalMap */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
經過上邊的分析,咱們發現,ThreadLocal類的使用雖然是用來解決多線程的問題的,可是仍是有很明顯的針對性
1.最明顯的,ThreadLoacl變量的活動範圍爲某線程,而且個人理解是該線程「專有的,獨自霸佔」,對該變量的全部操做均有該線程完成!也就是說,ThreadLocal不是用來解決共享,競爭問題的。典型的應用莫過於Spring,Hibernate等框架中對於多線程的處理了
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }
這段代碼,每一個線程有本身的ThreadLocalMap,每一個ThreadLocalMap中根據須要初始加載threadSession,這樣的好處就是介於singleton與prototype之間,應用singleton沒法解決線程,應用prototype開銷又太大,有了ThreadLocal以後就行了,對於須要線程「霸佔」的變量用ThreadLocal,而該類實例的方法都可以共享。
2.關於內存泄漏:
雖然ThreadLocalMap已經使用了weakReference,可是仍是建議可以顯示的使用remove方法。