再談ThreadLocal

你們對於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獲取用權限信息作權限校驗等。

相關文章
相關標籤/搜索