ThreadLocal 內部實現和應用場景
很多人都知道java中有ThreadLocal這個類,但是知道ThreadLocal這個類具體有什麼作用,然後適用什麼樣的業務場景還是很少的。今天我就嘗試以自己的理解,來講解下ThreadLocal類的內部實現和應用場景,如果有什麼不對之處,還望大家指正。
首先明確一個概念,那就是ThreadLocal並不是用來併發控制訪問一個共同物件,而是為了給每個執行緒分配一個只屬於該執行緒的物件(這麼粗暴的解釋可能還不太準確),更準確的說是為了實現執行緒間的資料隔離。而ThreadLocal應用場景更多是想共享一個變數,但是該變數又不是執行緒安全的,那麼可以用ThreadLocal維護一個執行緒一個例項。有時候ThreadLocal也可以用來避免一些引數傳遞,通過ThreadLocal來訪問物件。
首先我們先看下ThreadLocal(jdk1.7)內部的實現:
ThreadLocal get方法
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();
}
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);
}
get和set方法是ThreadLocal類中最常用的兩個方法。
get方法
程式碼很容易理解,首先我們通過Thread.currentThread得到當前執行緒,然後獲取當前執行緒的threadLocals變數,這個變數就是ThreadLocalMap型別的。然後根據當前的ThreadLocal例項作為key,獲取到Entry物件。
set方法
程式碼同樣很容易理解。同樣根據Thread.currentThread得到當前執行緒,如果當前執行緒存在threadLocals這個變數不為空,那麼根據當前的ThreadLocal例項作為key尋找在map中位置,然後用新的value值來替換舊值。
在ThreadLocal這個類中比較引人注目的應該是ThreadLocal->ThreadLocalMap->Entry這個類。這個類繼承自WeakReference。關於弱引用的知識,以後我會抽時間寫篇文章來介紹下。
最近在我們的web專案中servlet需要頻繁建立SimpleDateFormat這個物件,進行日期格式化。因為建立這個物件本身很費時的,而且我們也知道SimpleDateFormat本身不是執行緒安全的,也不能快取一個共享的SimpleDateFormat例項,為此我們想到使用ThreadLocal來給每個執行緒快取一個SimpleDateFormat例項,提高效能。同時因為每個Servlet會用到不同pattern的時間格式化類,所以我們對應每一種pattern生成了一個ThreadLocal例項。
DateFormatFactory
public class DateFormatFactory {
private static final Map<DatePattern, ThreadLocal<DateFormat>> pattern2ThreadLocal;
static {
DatePattern[] patterns = DatePattern.values();
int len = patterns.length;
pattern2ThreadLocal = new HashMap<DatePattern, ThreadLocal<DateFormat>>(len);
for (int i = 0; i < len; i++) {
DatePattern datePattern = patterns[i];
final String pattern = datePattern.pattern;
pattern2ThreadLocal.put(datePattern, new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
});
}
}
//獲取DateFormat
public static DateFormat getDateFormat(DatePattern pattern) {
ThreadLocal<DateFormat> threadDateFormat = pattern2ThreadLocal.get(pattern);
//不需要判斷threadDateFormat是否為空
return threadDateFormat.get();
}
}
DatePattern 列舉類
public enum DatePattern {
TimePattern("yyyy-MM-dd HH:mm:ss"),
DatePattern("yyyy-MM-dd"),
public String pattern;
private DatePattern(String pattern) {
this.pattern = pattern;
}
}
這樣我們就可以每次呼叫DateFormatFactory.getDateFormat獲取到對應的時間格式化類了。之前我們提到使用ThreadLocal同時可以避免參數傳遞。假如我們這個Servlet要呼叫到其他類的方法,而且方法內需要使用時間格式化類。按照正常情況下我們把該時間格式化類作為引數進行傳遞,但如果有了ThreadLocal這個類,我們可以不需要作為引數傳遞了,可以在方法類通過ThreadLocal得到時間格式化類。當然程式碼的通用性就差了。