Posted on 2010-09-10 13:44
xiaolang 閱讀(5643)
評論(0) 編輯 收藏
首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象。
另外,說ThreadLocal使得各線程能夠保持各自獨(dú)立的一個對象,并不是通過ThreadLocal.set()來實現(xiàn)的,而是通過每個線程中的new 對象
的操作來創(chuàng)建的對象,每個線程創(chuàng)建一個,不是什么對象的拷貝或副本。
個人版中有一個比較好的應(yīng)用場景,就是為會員查詢服務(wù)做的catche。
在用戶的一次請求中需要多次查詢用戶的卡信息,會多次去調(diào)用遠(yuǎn)程服務(wù),造成性能上的浪費(fèi)。如果能在本地做一個簡單的cache,就可以省去遠(yuǎn)程調(diào)用的開銷,就可以如下圖所示
從圖中可以看出,優(yōu)化后的將一部分cache存在本地,就省去了遠(yuǎn)程調(diào)用的開銷,從一定程度上減輕了會員核心的壓力。
Cache一般都有一個刷新時間的問題,時間長了,信息可能不準(zhǔn)確,時間短了,比較耗費(fèi)性能。最好的情況是這個cache過期了,不需要使用了,立即把清除,廢棄,這個最理想的情況。
大家應(yīng)該知道,用戶從發(fā)起請求,到服務(wù)器響應(yīng)的這個過程中,在服務(wù)器中是在一個線程中的。如果我們吧查詢出來的東西放到這個線程中,到用戶請求結(jié)束時,把這些東西清理掉,應(yīng)該是一個不錯的cache方案。
接下來的問題就簡單了,我們只需要把查詢的東西放到threadLocal就可以了。
看大牛李磊如何實現(xiàn)這一點
1. 首先需要初始化一個threadLocal
/** 用于將會員信息保存在本地的線程變量*/
private static
ThreadLocal<List<UserInfo>>
localUserInfo = new
ThreadLocal<List<UserInfo>>() {
protected synchronized List<UserInfo> initialValue() {
return new ArrayList<UserInfo>();
} };
2. 將查詢出來的東西放到threadLocal,下次查詢的時候,就可以先到threadLocal中取,如果沒有,再調(diào)用遠(yuǎn)程服務(wù)
UserInfo userInfo = getFromLocalByCardNo(cardNo);
if (userInfo == null) {
userInfo =
userInfoQueryService.findUserInfo(cardNo);
putToLocal(userInfo);
}
3.用戶的請求結(jié)束,把這些內(nèi)容清理掉。
為什么要把內(nèi)容清理掉
如果一直存放在threadLocal,
1是比較耗費(fèi)內(nèi)存;
2是里面的內(nèi)容可能是過期的(用戶修改了信息,threadLocal里面沒有更新)
3請求結(jié)束后需要清除ThreadLocal的一個很重要的原因就是,如果使用了線程池,在請求結(jié)束后,線程的生命周期還沒有結(jié)束,而是放回到池中,這樣下次再使用此線程的時候就會獲得上次的上下文了。
我們?nèi)绾巫龅竭@一點呢?
開始之前先給大家介紹一個接口,spring mvc中的。HandlerInterceptor
這個接口中有三個方法
preHandle
在一個該方法會在Controller的方法執(zhí)行前會被調(diào)用,可以使用這個方法來中斷或者繼續(xù)執(zhí)行鏈的處理,當(dāng)返回true時,處理執(zhí)行鏈會繼續(xù),當(dāng)返回false時,則不會去執(zhí)行Controller的方法
postHandle
這 3個方法會在在controller的方法執(zhí)行之后,在DispatcherServlet類導(dǎo)向到view進(jìn)行render之前依次執(zhí)行。最有用的是使 用postHandleRender方法,因為它有ModelAndView 傳進(jìn)來,那么我們就可以在render view之前往view中添加額外的model對象,或者對view的去處進(jìn)行修改(例如下面的“用HTML還是用Excel來作為View ”例子就是對view進(jìn)行了更改)
afterCompletion
該方法會在render view完成后執(zhí)行,也可以說在請求過程(request processing)完成之后執(zhí)行。該方法可以用來清理資源(例如象blackboard
building block release context)
我們只需要在afterCompletion中把本次線程中的存放的信息清理掉,就可以了。
大家應(yīng)該記得這一段代碼,初始化threadLocal并不需要這一點,
static {
ThreadLocalCleaner.register(localUserInfo);
ThreadLocalCleaner.register(localCertifyStatus);
}
讓我們先看下ThreadLocalCleaner的代碼
/**
* 清理所有的線程變量。
*/
public static void clear() {
for (ThreadLocal<?> tl : tls)
{
tl.remove();
}
}
/**
* @param tl
*/
public static void register(ThreadLocal<?> tl) {
tls.add(tl);
}
方 法register將初始化的ThreadLocal放到一個總的ThreadLocal里,方法clear將總的ThreadLocal里面的內(nèi)容清 空。我們只需要在HandlerInterceptor實現(xiàn)類中的afterCompletion方法中把clear調(diào)用一下就可以了。