前言
java猿在面試中,經常會被問到1個問題:
java實現同步有哪幾種方式?
大家一般都會回答使用synchronized, 那么還有其他方式嗎? 答案是肯定的, 另外一種方式也就是本文要說的ThreadLocal。
ThreadLocal介紹
ThreadLocal, 看名字也能猜到, "線程本地", "線程本地變量"。 我們看下官方的一段話:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
粗略地翻譯一下:
ThreadLocal這個類提供線程本地的變量。這些變量與一般正常的變量不同,它們在每個線程中都是獨立的。ThreadLocal實例最典型的運用就是在類的私有靜態變量中定義,並與線程關聯。
什么意思呢? 下面我們通過1個實例來說明一下:
jdk中的SimpleDateFormat類不是一個線程安全的類,在多線程中使用會出現問題,我們會通過線程同步來處理:
-
使用synchronized
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public synchronized static String formatDate(Date date) { return sdf.format(date); }
-
使用ThreadLocal
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public String formatIt(Date date) { return formatter.get().format(date); }
這兩種方式是一樣的,只不過一種用了synchronized,另外一種用了ThreadLocal。
synchronized和ThreadLocal的區別
使用synchronized的話,表示當前只有1個線程才能訪問方法,其他線程都會被阻塞。當訪問的線程也阻塞的時候,其他所有訪問該方法的線程全部都會阻塞,這個方法相當地 "耗時"。
使用ThreadLocal的話,表示每個線程的本地變量中都有SimpleDateFormat這個實例的引用,也就是各個線程之間完全沒有關系,也就不存在同步問題了。
綜合來說:使用synchronized是一種 "以時間換空間"的概念, 而使用ThreadLocal則是 "以空間換時間"的概念。
ThreadLocal原理分析
我們先看下ThreadLocal的類結構:
我們看到ThreadLocal內部有個ThreadLocalMap內部類,ThreadLocalMap內部有個Entry內部類。
先介紹一下ThreadLocalMap和ThreadLocalMap.Entry內部類:
ThreadLocalMap其實也就是一個為ThreadLocal服務的自定義的hashmap類。
Entry是一個繼承WeakReference類的類,也就是ThreadLocalMap這個hash map中的每一項,並且Entry中的key基本上都是ThreadLocal。
再下來我們看下Thread線程類:
Thread線程類內部有個ThreadLocal.ThreadLocalMap類型的屬性:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
下面重點來看ThreadLocal類的源碼:
public T get() {
// 得到當前線程
Thread t = Thread.currentThread();
// 拿到當前線程的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 找到該ThreadLocal對應的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 當前線程沒有ThreadLocalMap對象的話,那么就初始化ThreadLocalMap
return setInitialValue();
}
private T setInitialValue() {
// 初始化ThreadLocalMap,默認返回null,可由子類擴展
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 實例化ThreadLocalMap之后,將初始值丟入到Map中
map.set(this, value);
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
// set邏輯:找到當前線程的ThreadLocalMap,找到的話,設置對應的值,否則創建ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
注釋已經寫了,讀者有不明白的可以自己看看源碼。
ThreadLocal的應用
ThreadLocal應用廣泛,下面介紹下在SpringMVC中的應用。
RequestContextHolder內部結構
RequestContextHolder:該類會暴露與線程綁定的RequestAttributes對象,什么意思呢? 就是說web請求過來的數據可以跟線程綁定, 用戶A,用戶B分別請求過來,可以使用RequestContextHolder得到各個請求的數據。
RequestContextHolder數據結構:
具體這兩個holder:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
這里的NamedThreadLocal只是1個帶name屬性的ThreadLocal:
public class NamedThreadLocal<T> extends ThreadLocal<T> {
private final String name;
public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
繼續看下RequestContextHolder的getRequestAttributes方法,其中接口RequestAttributes是對請求request的封裝:
public static RequestAttributes getRequestAttributes() {
// 直接從ThreadLocalContext拿當前線程的RequestAttributes
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
我們看到,這里直接使用了ThreadLocal的get方法得到了RequestAttributes。
當需要得到Request的時候執行:
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
RequestContextHolder的初始化
以下代碼在FrameworkServlet代碼中:
總結
本文介紹了ThreadLocal的原理以及ThreadLocal在SpringMVC中的應用。個人感覺ThreadLocal應用場景更多是共享一個變量,但是該變量又不是線程安全的,而不是線程同步。比如RequestContextHolder、LocaleContextHolder、SimpleDateFormat等的共享應用。