JAVA ThreadLocal 對象 ServletActionContext

來源:互聯網
上載者:User

標籤:threadlocal   threadlocalmap   hibernate   session   thread   

最近在開發過程中,在做一個字典項服務的時候,最開始採用了ThreadLocal對象來快取資料。在使用ThreadLocal過程中遇到一些問題,這裡和大家分享一下。

一、 什麼是ThreadLocal

顧名思義它是local variable(線程局部變數)。它的功用非常簡單,就是為每一個使用該變數的線程都提供一個變數值的副本。從線程的角度看,就好像每一個線程都完全擁有該變數。

它主要由四個方法組成initialValue(),get(),set(T),remove(),其中initialValue()方法是一個protected的方法,只有在重寫ThreadLocal的時候有用。

void set(T t):為調用該方法的線程存入一個本線程變數。

T get(): 返回本線程存入ThreadLocal中的值,沒有返回空。

void remove(): 移除本線程存入ThreadLocal中的值。

T initialValue():用於在為null時,產生一個初始值,ThreadLocal直接返回一個null值。

二、 ThreadLocal的原理

在查看了java源碼後發現,ThreadLocal通過使用ThreadLocalMap(註:這裡的Map非java.util.Map子類)執行個體來儲存”線程局部變數”,當第一次設值的時候,如果map為空白,則建立一個map並set入值,但是這個儲值的Map並非ThreadLocal的成員變數,而是java.lang.Thread 類的成員變數。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)                return (T)e.value;        }        return setInitialValue();}

程式碼片段1

三、 ThreadLocal的對象釋放問題

3.1 在我們使用ThreadLocal過程中,線程結束後,它的”線程局部變數”是如何回收的呢?

首先,儲存”線程局部變數”的map並非是ThreadLocal的成員變數, 而是java.lang.Thread的成員變數。也就是說,線程結束的時候,該map的資源也同時被回收。

解析:

ThreadLocal的set,get方法中均通過如下方式擷取Map:

ThreadLocalMap map = getMap(t);

而getMap方法的代碼如下:

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

程式碼片段2

可見:ThreadLocalMap執行個體是作為java.lang.Thread的成員變數儲存的,每個線程有唯一的一個threadLocalMap。這個map以ThreadLocal對象為key,”線程局部變數”為值,所以一個線程下可以儲存多個”線程局部變數”。對ThreadLocal的操作,實際委託給當前Thread,每個Thread都會有自己獨立的ThreadLocalMap執行個體,儲存的倉庫是Entry[] table;Entry的key為ThreadLocal,value為儲存內容;因此在並發環境下,對ThreadLocal的set或get,不會有任何問題。以下為”線程局部變數”的儲存圖:

“線程局部變數”的儲存圖

由於treadLocalMap是java.util.Thread的成員變數,threadLocal作為threadLocalMap中的key值,在一個線程中只能儲存一個”線程局部變數”。將ThreadLocalMap作為Thread類的成員變數的好處是:

a. 當線程死亡時,threadLocalMap被回收的同時,儲存的”線程局部變數”如果不存在其它引用也可以同時被回收。

b. 同一個線程下,可以有多個treadLocal執行個體,儲存多個”線程局部變數”。

3.2 如果線程線上程池中,一直存在,而threadLocal在多個地方被迴圈放入,會不會造成threadLocal對象無法回收?

如下所示:

public class TestMain {public static void main(String[] args) {while (true) {for (int j = 0; j < 10; j++) {new ThreadLocalDomail(new byte[1024*1024]).getAndPrint();}}}}class ThreadLocalDomail{private ThreadLocal<byte[]> threadLocal=new ThreadLocal< byte[]>();publicThreadLocalDomail(byte[] b){ threadLocal.set(b);}public byte[] getAndPrint(){ byte[] b=threadLocal.get(); System.out.println(b.length); return b;}}

        程式碼片段3

因為ThreadLocalMap的Entry是(weakReference)弱引用,在外部不再引用threadLocal對象時,線程map中threadLocal對應的key及其value均會被釋放,不會造成記憶體溢出。以上TestMain代碼中的new ThreadLocalDomail在每次迴圈後即被丟棄,可被記憶體回收行程回收,代碼可持續運行,不會記憶體溢出。

四、 ThreadLocal的應用

在比較熟悉的兩個架構中,Struts2和Hibernate均有採用ThreadLocal變數,而且對整個架構來說是非常核心的一部分。

Struts2和Struts1的一個重要升級就是對request,response兩個對象的解耦,Struts2的Action方法中不再需要傳遞request,response參數。但是Struts2不通過方法直接傳入request,response對象,那麼這兩個值是如何傳遞的呢?

Struts2採用的正是ThreadLocal變數。在每次接收到請求時,Struts2在調用攔截器和action前,通過將request,response對象放入ActionContext執行個體中,而ActionContext執行個體是作為”線程局部變數”存入ThreadLocal actionContext中。

public class ActionContext implements Serializable {    static ThreadLocal actionContext = new ThreadLocal();. . .

        程式碼片段4

由於actionContext是”線程局部變數”,這樣我們通過ServletActionContext.getRequest()即可獲得本線程的request對象,而且在本地線程的任意類中,均可通過該方法擷取”線程局部變數”,而無需值傳遞,這樣Action類既可以成為一個simple類,無需繼承struts2的任意父類。

在利用Hibernate開發DAO模組時,我們和Session打的交道最多,所以如何合理的管理Session,避免Session的頻繁建立和銷毀,對於提高系統的效能來說是非常重要的。一般常用的Hibernate工廠類,都會通過ThreadLocal來儲存線程的session,這樣我們在同一個線程中的處理,工廠類的getSession()方法,即可以多次擷取同一個Session進行操作,closeSession方法可在不傳入參數的情況下,正確關閉session。

五、 使用 ThreadLocal發生的問題

在WEB伺服器環境下,由於Tomcat,weblogic等伺服器有一個線程池的概念,即接收到一個請求後,直接從線程池中取得線程處理請求;請求響應完成後,這個線程本身是不會結束,而是進入線程池,這樣可以減少建立線程、啟動線程的系統開銷。

由於Tomcat線程池的原因,我最初使用的”線程局部變數”儲存的值,在下一次請求依然存在(同一個線程處理),這樣每次請求都是在本線程中取值而不是去memCache中取值,如果memCache中的資料發生變化,也無法及時更新。

解決方案: 處理完成後主動調用該業務treadLocal的remove()方法,將”線程局部變數”清空,避免本線程下次處理的時候依然存在舊資料。由於主動清理需要使用struts2攔截器,為了簡單的解決問題,最後通過ServletActionContext.getRequest()擷取request後,將資料setAttribute進request對象中,美中不足的是和request對象有一定的耦合。

Sturts2是如何解決線程池的問題呢?

由於web伺服器的線程是多次使用的,很顯然Struts2在響應完成後,會主動的清除“線程局部變數”中的ActionContext值,在struts2的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter類中,有這樣的程式碼片段:

finally {

prepare.cleanupRequest(request);

}

而cleanupRequest方法中有如下代碼

    public void cleanupRequest(HttpServletRequest request) {……//省略部分代碼        ActionContext.setContext(null);        Dispatcher.setInstance(null);    }

        程式碼片段6

由此可見,Sturts2在處理完成後,會主動清空”線程局部變數”ActionContext,來達到釋放系統資源的目的。

六、 總結

使用ThreadLocal的幾點建議:

1. ThreadLocal應定義為靜態成員變數,程式碼片段3中的定義方式是不提倡的。

2. 能通過傳值傳遞的參數,不要通過ThreadLocal儲存,以免造成ThreadLocal的濫用。

3. 線上程池的情況下,在ThreadLocal業務周期處理完成時,最好顯式的調用remove()方法,清空”線程局部變數”中的值。

4. 正常情況下使用ThreadLocal不會造成記憶體溢出,但如3.2中所述,弱引用的只是threadLocal,儲存的值依然是強引用的,如果threadLocal依然被其他對象強引用,”線程局部變數”是無法回收的。

以上是本人對ThreadLocal對象的一些瞭解,如有不足,還請指正。

JAVA ThreadLocal 對象 ServletActionContext

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.