1. 程式人生 > >ThreadLocal 內部實現和應用場景

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得到時間格式化類。當然程式碼的通用性就差了。