- 訪問線程不安全對象。比如下面SimpleDateFormat的使用,這是個線程不安全的類,不想每次都new一個對象用完即棄?把它放進ThreadLocal裏吧,這樣每個線程就只擁有一個實例了。
- 存放線程級別的狀態對象。例如上下文對象、用戶會話這種對象,不想在各個方法調用中層層傳遞?把它放進ThreadLocal裏面把,這樣在同一個線程任何一個地方都可以獲取。
二、示例
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
new MyThread().start();
Thread.sleep(2000);
new MyThread().start();
}
}
class MyThread extends Thread {
//SimpleDateFormat爲線程不安全的,因此使用ThreadLocal在每個線程保存一個實例
public static final ThreadLocal<SimpleDateFormat> DATE_FORMATER = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
};
//模擬放入一個上下文對象,可以在當前線程內進行讀寫
public static final ThreadLocal<Map<String, String>> CONTEXT = new ThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<String, String>();
}
};
@Override
public void run() {
CONTEXT.get().put("id", "Thread_" + System.currentTimeMillis());
try {
while (true) {
String id = CONTEXT.get().get("id");
System.out.println(id + ": " + DATE_FORMATER.get().format(new Date()));
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三、源碼
-
首先看Thread類,每個Thread都有一個變量threadLocals,存放所有對應這個線程的ThreadLocal。他的類型是ThreadLocal.ThreadLocalMap,其實就相當與一個特殊的Map,Key是ThreadLocal對象,value就是我們需要存放的線程本地變量,這裏就不對這個對象深入研究了。
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* ...... */ }
-
回到ThreadLocal,看看用來獲取保存的線程本地變量的get方法,其實操作的是當前線程的threadLocals:
public T get() { //獲取當前線程 Thread t = Thread.currentThread(); //把線程的threadLocals變量拿出來 ThreadLocalMap map = getMap(t); if (map != null) { //通過ThreadLocal對象自身作爲key去拿出Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) //找到了,就把值返回 return (T)e.value; } //如果threadLocals變量爲空,則需要初始化 return setInitialValue();
-
在get方法的最後,調用了setInitialValue(),繼續看看源碼:
private T setInitialValue() { //調用initialValue方法獲取初始值,這就是爲什麼我們創建ThreadLocal //對象的時候需要重寫這個方法來提供我們需要保存的值 T value = initialValue(); //獲取當前線程 Thread t = Thread.currentThread(); //獲取線程裏面的threadLocals對象 ThreadLocalMap map = getMap(t); if (map != null) //把當前ThreadLocal對象作爲key,初始值作爲value,放進去 map.set(this, value); else //如果這個屬性爲空,則初始化並把相應的值放進去 createMap(t, value); return value; }
-
有get就要有set,理解了setInitialValue,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);
四、InheritableThreadLocal
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
恩,沒錯,其實就是在父線程創建子線程的時候,把inheritableThreadLocals變量的值全部複製給子線程。