ThreadLocal原理和項目中如何使用

本教程分如下三個部分
1. 項目中如何使用Threadlocal
2. Threadlocal和Thread關係以及Threadlocal源碼分析
3. Threadlocal的DEMO練習(提供github和碼雲下載源代碼)

首先上乾貨。講講我司項目中如何使用ThreadLocal。
會話保持靜態類
這是一個登錄會話保持的靜態類,用來保存當前線程的登錄信息。
使用AssertionContent原因:
由於通過ServletRequest request轉換成

HttpServletRequest httpRequest = ( HttpServletRequest )request;

可以通過HttpSession session = httpRequest.getSession( false );得到session。但是,並不是每個業務類或者方法都能得到當前的httpRequest。所以,就可以使用ThreadLocal在任何當前線程的任何業務類中,得到session。
在登錄的filter中,可以通過session.setAttribute( BrowserSession.ASSERTION, bs );將當前BrowserSession保存到會話中。

BrowserSession bs = new BrowserSession();
        bs.setUid(userId);
        bs.setUname(name);
        ...

BrowserSession 是當前用戶登錄的一些個人信息。是業務自定義的實體類。
下一次再訪問的時候,可以直接從session中得到用戶所有信息。

在業務相關的filter中,使用線程變量Threadlocal保持會話
其中set方法如下:

public void setAttribute( String name, Object value ) {
        attributes.put( name, value );
    }

以上set方法就是將session會話保存到AssertionContext
initContext
注意:此處是用一個AssertionContext中的一個Threadlocal變量保存了session。
所以在取session的時候,使用如下圖所示方法:
取session
其中AssertionContex.getContext()方法大致如下:

public static AssertionContext getContext() {
        AssertionContext context = contextHolder.get();
        ...
        return context;
    }

其實也可以將以上步驟放到一個filter中,怎麼順手怎麼使用。
接下來,就是在任何你想要使用session的地方,取得session,比如在業務service中:
業務service
其中checkLogin方法就是通過上面說到的先得到AssertionContext 中的Threadlocal再得到session來獲得。

以上是講解如何在業務中使用Threadlocal,下面結合源碼介紹下原理:
Threadlocal設計兩個java類:Thread和Threadlocal。
首先講解下他們之間的關係

Thread和Threadlocal關係
可以看到,Thread有個成員變量ThreadLocal.ThreadLocalMap,而ThreadLocalMap是Threadlocal的內部靜態類。ThreadLocalMap中存的值的key爲this,即當前Threadlocal類的引用,這樣,每次從同一個Threadlocal和同一個Thread中得到的值就唯一確定了。
看一下Threadlocal的set方法:

  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

首先得到當前線程,然後得到當前線程的ThreadlocalMap。由於只要線程確定,所以map就確定。
getMap方法如下:

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

直接放回當前線程的成員變量。
可以看到第一次得到的map肯定爲null,我們接下來看createMap方法:

  void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

直接new了一個ThreadLocalMap,其中key爲當前Threadlocal的引用。
如果map的值不爲null則直接把值set到map中去。
看完set方法接着看get方法:

    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();
    }

首先,還是得到當前線程,得到當前線程的map,如果map不爲空,則:
得到key爲this的值,然後返回該值。如果map爲空則調用setInitialValue方法

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

繼續判斷map是否爲空(這裏再進行判斷,我認爲可能是在多個線程併發執行的情況下,如果執行到方法的入口處,其他的線程又new了一個ThreadlocalMap,辣麼此處就不需要再new了),這裏的initialValue()方法直接放回的是一個null。
到此,Threadlocal的set和get方法和原理都介紹完了。需要注意的是,由於ThreadlocalMap中的key是this引用,也就是說,this引用如果指向不同的對象,辣麼通過get方法得到的值就不是希望得到的那個值。所以,要想每次得到的值都是正確的,必須使this指針指向的對象唯一,這就解釋了爲什麼Threadlocal都使用靜態變量來保存。

爲了更好的理解Threadlocal的原理,下面有幾個Threadlocal的demo練習,非常簡單。大家可以clone下來或者fork下來試試。
git地址:
https://github.com/tengqingya/ThreadLocalPractice
碼雲地址:
https://git.oschina.net/tengqingya/ThreadLocalPractice

請尊重作者和版權,轉載請標明出處。
聯繫作者:qq475804848,滕慶亞

發佈了25 篇原創文章 · 獲贊 37 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章