本教程分如下三個部分
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中,
其中set方法如下:
public void setAttribute( String name, Object value ) {
attributes.put( name, value );
}
以上set方法就是將session會話保存到AssertionContext
注意:此處是用一個AssertionContext中的一個Threadlocal變量保存了session。
所以在取session的時候,使用如下圖所示方法:
其中AssertionContex.getContext()方法大致如下:
public static AssertionContext getContext() {
AssertionContext context = contextHolder.get();
...
return context;
}
其實也可以將以上步驟放到一個filter中,怎麼順手怎麼使用。
接下來,就是在任何你想要使用session的地方,取得session,比如在業務service中:
其中checkLogin方法就是通過上面說到的先得到AssertionContext 中的Threadlocal再得到session來獲得。
以上是講解如何在業務中使用Threadlocal,下面結合源碼介紹下原理:
Threadlocal設計兩個java類: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,滕慶亞