你們對於ThreadLocal確定很熟悉了,可是真正在項目中使用過的估計就很少了,有的牛人也許已經使用n屢次了。java
面向人羣web
對ThreadLocal不是很熟或者熟悉一些可是沒用過,甚至用過了也沒用去理解他的實現原理的同窗。spring
主要內容後端
ThreadLocal的簡介緩存
ThreadLocal的實現原理數據結構
ThreadLocal的 部分源碼分析ide
ThreadLocal在項目中的使用源碼分析
ThreadLocal簡介this
每一個線程都包含對其本地線程副本的隱式引用變量,只要線程處於活動狀態,實例是可訪問的;spa
線程消失後,它的全部副本線程本地實例接受垃圾收集(除非存在對這些副本的其餘引用)。
簡單理解爲每一個線程副本,每一個線程都有本身的副本,相互之間並不知曉。
ThreadLocal原理
先說JVM虛擬運行時區:
其中虛擬機棧是線程執行方法(字節碼)的地方,每建立一個線程實例,就是分配固定大小(Xss設置參數)的內存空間給線程執行方法用,具體結構圖以下:
棧幀(每調一個方法就入棧一個棧幀到棧裏):
上面已經大概的瞭解了一下堆棧,那就再來看看線程的堆棧與本地變量的一個關係結構圖:
咱們知道,線程實例也是一個對象,對象都是存放在堆裏面的。從上圖能夠看出,當建立一個新的線程,那麼就會有一個相對應的堆棧空間建立,那個stack部分就能夠很好的解析上文提到的一句話「Java線程的建立,除了堆棧空間,每一個線程還須要爲線程本地存儲(thread-local storage)和內部數據結構提供一些本機內存」。這個stack區域就是堆棧空間,而在這個堆棧空間裏面有兩個直線了堆棧對象的引用,一個是線程實例的應用,另外一個就是本地變量的引用。
ThreadLocal的部分源碼分析
先看其大概:
先看set方法
//設值 public void set(T value) { //獲取當前線程 Thread t = Thread.currentThread(); //獲取ThreadLocal.ThreadLocalMap threadLocals變量 ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) //若是map不爲空,則以當前ThreadLocal爲key, //value爲map的value放進去 map.set(this, value); else //若是map爲空,t.threadLocals = new ThreadLocalMap(this, firstValue); createMap(t, value); }
由此可一看得出,ThreadLocal和Map有那麼些關係或者說關聯。
static class ThreadLocalMap { //初始化大小16,必須是2的n次方 //若是理解HashMap的設計原理的話,這個map就很容易理解 }
原理這個map不是咱們經常使用的java.util.Map,只不過這個map的設計原理和Map很像。繼續看這個map中
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
這個Entry原來是WeakReference的子類。這裏有聯想到對象的四大引用了:強、軟、弱、虛。這裏剛剛是使用了弱引用。那麼什麼損失弱引用呢?只要垃圾回收機制一旦運行,無論 JVM 的內存空間是否足夠,總會回收該對象佔用的內存,想了解更多關於這裏使用弱引用的問題,請看
remove方法:
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
刪除此線程的當前線程的本地值變量。若是此線程局部變量隨後被當前線程讀取,其值將爲經過調用其initialValue方法從新初始化,除非其值是由當前線程設置在此期間。不然這可能致使屢次調用當前線程中的方法initialvalue。
get方法
//獲取值 public T get() { //獲取當前線程 Thread t = Thread.currentThread(); //獲取當前線程的ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) { //獲取value ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //返回一個null return setInitialValue(); }
ThreadLocal的在項目中的使用
大體需求:
每次http請求到後臺都會攜帶用戶的信息:
id/name/mobile
而且都是放header中。而後後端經過從header中獲取,而後保存到本地,直到這個線程結束而後隨之清楚。
public class UserContext { private static final Logger LOGGER = LoggerFactory.getLogger(TreasurerUserContext.class); private static final ThreadLocal<Map<String, Object>> contextData = new ThreadLocal(); public static final String X_USER_ID = "userId"; public static final String X_TOKEN = "token"; public static final String X_NAME = "name"; public static final String X_MOBILE = "moblie"; public static final String X_USER = "user"; public TreasurerUserContext() { } public static Integer getUserId() { return getIntegerValue(X_USER_ID); } public static void putUserId(Object value) { put(X_USER_ID, value); } public static String getUserName() { return getStringValue(X_NAME); } public static void putUserName(Object value) { put(X_NAME, value); } public static String getMobile() { return getStringValue(X_USER_ID); } public static void putMobile(Object value) { put(X_MOBILE, value); } public static String getToken() { return getStringValue(X_TOKEN); } public static void putToken(Object value) { put(X_TOKEN, value); } public static void put(String key, Object value) { ((Map) contextData.get()).put(key, value); } public static void init() { contextData.set(new HashMap()); } public static void remove() { contextData.remove(); } private static Integer getIntegerValue(String key) { if (contextData.get() == null) { return null; } else { Object value = ((Map) contextData.get()).get(key); return value == null ? null : Integer.parseInt(value.toString()); } } private static String getStringValue(String key) { if (contextData.get() == null) { return null; } else { Object value = ((Map) contextData.get()).get(key); return value == null ? null : (String) value; } } }
作個切面
@Aspect @Component public class RequestAspect { //本身定義 @Pointcut("(@target(org.springframework.web.bind.annotation.RestController)) && (execution(public * com.lawt.user..*.*(..)))") public void executionService() { } /** * 方法調用以前調用 */ @Before("executionService()") public void doBefore() { LOGGER.info("開始處理請求頭部信息!"); UserContext.init(); ServletRequestAttributes requestAttributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()); if (requestAttributes == null) { LOGGER.info("ServletRequestAttributes is null"); return; } HttpServletRequest request = requestAttributes.getRequest(); // mobile String mobile = request.getHeader(UserContext.X_MOBILE); if (StringUtils.isNotEmpty(mobile)) { UserContext.putMobile(mobile); } // userId String userId = request.getHeader(UserContext.X_USER_ID); if (StringUtils.isNotEmpty(userId)) { UserContext.put(UserContext.X_USER_ID, userId); } // userName String userName = request.getHeader(UserContext.X_NAME); if (StringUtils.isNotEmpty(userName)) { UserContext.put(UserContext.X_NAME, userName); } /** * 方法以後調用 清楚線程緩存 */ @AfterReturning(pointcut = "executionService()") public void doAfterReturning() { LOGGER.info("清楚數據"); UserContext.remove(); } }
而後在本身的controller類裏或service中就能夠直接使用
Integer userId= UserContext.getUserId();
拿到用戶信息後就能夠作一些相關操做,好比經過userId獲取用權限信息作權限校驗等。