ThreadLocal<T>類在Spring,Hibernate等框架中起到了很大的作用,對於其工作原理,很多網上的文章分析的不夠徹底,甚至有些誤解。
首先,爲了解釋ThreadLocal類的工作原理,必須同時介紹與其工作甚密的其他幾個類(內部類)
1.ThreadLocalMap
2.Thread
可能有人會覺得Thread與ThreadLocal有什麼關係,其實真正的奧祕就在Thread類中的一行:
- ThreadLocal.ThreadLocalMap threadLocals = null;
其中ThreadLocalMap的定義是在ThreadLocal類中,真正的引用卻是在Thread類中
那麼ThreadLocalMap究竟是什麼呢?
可以看到這個類應該是一個Map,JDK的解釋是
接下來的重點是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究竟是如何工作的了
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方法。