ThreadLocal有點相似於Map類型的數據變量。ThreadLocal類型的變量每一個線程都有本身的一個副本,某個線程對這個變量的修改不會影響其餘線程副本的值。須要注意的是一個ThreadLocal變量,其中只能set一個值。java
ThreadLocal<String> localName = new ThreadLocal(); localName.set("name1"); String name = localName.get();
在線程1中初始化了一個ThreadLocal對象localName,並經過set方法,保存了一個值,同時在線程1中經過 localName.get()能夠拿到以前設置的值,可是若是在線程2中,拿到的將是一個null。安全
下面來看下ThreadLocal的源碼:數據結構
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
能夠發現,每一個線程中都有一個 ThreadLocalMap數據結構,當執行set方法時,其值是保存在當前線程的 threadLocals變量中,當執行get方法中,是從當前線程的 threadLocals變量獲取。 (ThreadLocalMap的key值是ThreadLocal類型)多線程
因此在線程1中set的值,對線程2來講是摸不到的,並且在線程2中從新set的話,也不會影響到線程1中的值,保證了線程之間不會相互干擾。ide
上面提到ThreadLoal的變量都是存儲在ThreadLoalMap的變量中,下面給出下Thread、ThreadLoal和ThreadLoalMap的關係。工具
Thread類有屬性變量threadLocals (類型是ThreadLocal.ThreadLocalMap),也就是說每一個線程有一個本身的ThreadLocalMap ,因此每一個線程往這個ThreadLocal中讀寫隔離的,而且是互相不會影響的。一個ThreadLocal只能存儲一個Object對象,若是須要存儲多個Object對象那麼就須要多個ThreadLocal!性能
說完ThreadLocal的原理,咱們來看看ThreadLocal的使用場景。this
1. 保存線程上下文信息,在任意須要的地方能夠獲取
好比咱們在使用Spring MVC時,想要在Service層使用HttpServletRequest。一種方式就是在Controller層將這個變量傳給Service層,可是這種寫法不夠優雅。Spring早就幫咱們想到了這種狀況,並且提供了現成的工具類:線程
public static final HttpServletRequest getRequest(){ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return request; } public static final HttpServletResponse getResponse(){ HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); return response; }
上面的代碼就是使用ThreadLocal實現變量在線程各處傳遞的。日誌
2. 保證某些狀況下的線程安全,提高性能
性能監控,如記錄一下請求的處理時間,獲得一些慢請求(如處理時間超過500毫秒),從而進行性能改進。這邊咱們以Spring MVC的攔截器功能爲列子。
public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter { //NamedThreadLocal是Spring對ThreadLocal的封裝,原理同樣 //在多線程狀況下,startTimeThreadLocal變量必須每一個線程之間隔離 private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime"); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception { //一、開始時間 long beginTime = System.currentTimeMillis(); //線程綁定變量(該數據只有當前請求的線程可見) startTimeThreadLocal.set(beginTime); //繼續流程 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception { long endTime = System.currentTimeMillis();//二、結束時間 long beginTime = startTimeThreadLocal.get();//獲得線程綁定的局部變量(開始時間) long consumeTime = endTime - beginTime;//三、消耗的時間 if(consumeTime > 500) {//此處認爲處理時間超過500毫秒的請求爲慢請求 //TODO 記錄到日誌文件 System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime)); } } }
說明:其實要實現上面的功能,徹底能夠不用ThreadLocal(同步鎖等),可是上面的代碼的確是說明ThreadLocal這個是用場景很好的列子。
從上面的圖中能夠看到,Entry的key指向ThreadLocal用虛線表示弱引用 ,下面咱們來看看ThreadLocalMap:
java對象的引用包括 : 強引用,軟引用,弱引用,虛引用 。
弱引用也是用來描述非必需對象的,當JVM進行垃圾回收時,不管內存是否充足,該對象僅僅被弱引用關聯,那麼就會被回收。當僅僅只有ThreadLocalMap中的Entry的key指向ThreadLocal的時候,ThreadLocal會進行回收的!!!
ThreadLocal被垃圾回收後,在ThreadLocalMap裏對應的Entry的鍵值會變成null,可是Entry是強引用,那麼Entry裏面存儲的Object,並無辦法進行回收,因此ThreadLocalMap 存在內存泄露的風險。
因此最佳實踐,應該在咱們不使用的時候,主動調用remove方法進行清理。這裏給出一個建議方案:
public class Dynamicxx { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public void dosomething(){ try { contextHolder.set("name"); // 其它業務邏輯 } finally { contextHolder .remove(); } } }