標籤:== phi ssi 記憶體回收 基本 使用情境 而在 new http
問答形式總結:
1、 ThreadLocal類的作用
ThreadLocal的作用是提供線程內的局部變數,這種變數在多線程環境下訪問時能夠保證各個線程裡變數的獨立性。
ThreadLocal用於儲存某個線程共用變數:對於同一個static ThreadLocal,不同線程只能從中get,set,remove自己的變數,而不會影響其他線程的變數。
1、ThreadLocal.get: 擷取ThreadLocal中當前線程共用變數的值。
2、ThreadLocal.set: 設定ThreadLocal中當前線程共用變數的值。
3、ThreadLocal.remove: 移除ThreadLocal中當前線程共用變數的值。
4、ThreadLocal.initialValue: ThreadLocal沒有被當前線程賦值時或當前線程剛調用remove方法後調用get方法,返回此方法值。
2、 ThreadLocal原理,ThreadLocal是如何?的?
首先,在每個線程Thread內部有一個ThreadLocal.ThreadLocalMap類型的成員變數threadLocals,這個threadLocals就是用來儲存實際的變數副本的,索引值為當前ThreadLocal變數,value為變數副本(即T類型的變數)。
初始時,在Thread裡面,threadLocals為空白,當通過ThreadLocal變數調用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,並且以當前ThreadLocal變數為索引值,以ThreadLocal要儲存的副本變數為value,存到threadLocals。
然後在當前線程裡面,如果要使用副本變數,就可以通過get方法在threadLocals裡面尋找。
總結一下:
1)實際的通過ThreadLocal建立的副本是儲存在每個線程自己的threadLocals中的;
2)為何threadLocals的類型ThreadLocalMap的索引值為ThreadLocal對象,因為每個線程中可有多個threadLocal變數,就像上面代碼中的longLocal和stringLocal;
3)在進行get之前,必須先set,否則會報null 指標異常;
如果想在get之前不需要調用set就能正常訪問的話,必須重寫initialValue()方法。
因為在上面的程式碼分析過程中,我們發現如果沒有先set的話,即在map中尋找不到對應的儲存,則會通過調用setInitialValue方法返回i,而在setInitialValue方法中,有一個語句是T value = initialValue(), 而預設情況下,initialValue方法返回的是null。
3、 ThreadLocal的使用情境
最常見的ThreadLocal使用情境為 用來解決 資料庫連接、Session管理等。
如:
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { public Connection initialValue() { return DriverManager.getConnection(DB_URL); }}; public static Connection getConnection() { return connectionHolder.get(); }
下面這段代碼摘自:
http://www.iteye.com/topic/103804
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;}
4、 常說的ThreadLocal記憶體流失問題:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
從上面源碼可以看出,ThreadLocalMap使用ThreadLocal的弱引用作為Entry的key,如果一個ThreadLocal沒有外部強引用來引用它,下一次系統GC時,這個ThreadLocal必然會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value。
我們上面介紹的get、set、remove等方法中,都會對key為null的Entry進行清除(expungeStaleEntry方法,將Entry的value清空,等下一次記憶體回收時,這些Entry將會被徹底回收)。
但是如果當前線程一直在運行,並且一直不執行get、set、remove方法,這些key為null的Entry的value就會一直存在一條強引用練:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,導致這些key為null的Entry的value永遠無法回收,造成記憶體流失。
如何避免記憶體流失?
為了避免這種情況,我們可以在使用完ThreadLocal後,手動調用remove方法,以避免出現記憶體流失。
5、其他總結
總結:
- 每個線程都有一個ThreadLocalMap 類型的 threadLocals 屬性。
- ThreadLocalMap 類相當於一個Map,key 是 ThreadLocal 本身,value 就是我們的值。
- 當我們通過 threadLocal.set(new Integer(123)); ,我們就會在這個線程中的 threadLocals 屬性中放入一個索引值對,key 是 這個 threadLocal.set(new Integer(123))的threadlocal,value 就是值new Integer(123)。
- 當我們通過 threadlocal.get() 方法的時候,首先會根據這個線程得到這個線程的 threadLocals 屬性,然後由於這個屬性放的是索引值對,我們就可以根據鍵 threadlocal 拿到值。 注意,這時候這個鍵 threadlocal 和 我們 set 方法的時候的那個鍵 threadlocal 是一樣的,所以我們能夠拿到相同的值。
- ThreadLocalMap 的get/set/remove方法跟HashMap的內部實現都基本一樣,通過 "key.threadLocalHashCode & (table.length - 1)" 運算式計算得到我們想要找的索引位置,如果該索引位置的索引值對不是我們要找的,則通過nextIndex方法計算下一個索引位置,直到找到目標索引值對或者為空白。
- hash衝突:在HashMap中相同索引位置的元素以鏈表形式儲存在同一個索引位置;而在ThreadLocalMap中,沒有使用鏈表的資料結構,而是將(當前的索引位置+1)對length模數的結果作為相同索引元素的位置:源碼中的nextIndex方法,可以表達成如下公式:如果i為當前索引位置,則下一個索引位置 = (i + 1 < len) ? i + 1 : 0。
—————END—————
參考文章:1、 Java並發:ThreadLocal詳解 (JoonWhee)
2、Java並發編程:深入剖析ThreadLocal (海子)
Java並發編程:深入剖析ThreadLocal (總結)