本教程分如下三個部分
1. 項目中如何使用Threadlocal
2. Threadlocal和Thread關系以及Threadlocal源碼分析
3. Threadlocal的DEMO練習(提供github和碼雲下載下傳源代碼)
首先上幹貨。講講我司項目中如何使用ThreadLocal。
這是一個登入會話保持的靜态類,用來儲存目前線程的登入資訊。
使用AssertionContent原因:
由于通過ServletRequest 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,滕慶亞