ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

再谈ThreadLocal

2020-12-13 22:51:59  阅读:178  来源: 互联网

标签:String 再谈 value ThreadLocal static 线程 public


大家对于ThreadLocal肯定很熟悉了,但是真正在项目中使用过的估计就不多了,有的牛人也许已经使用n多次了。

面向人群

对ThreadLocal不是很熟或者熟悉一些但是没用过,甚至用过了也没用去理解他的实现原理的同学。

主要内容

  • ThreadLocal的简介

  • ThreadLocal的实现原理

  • ThreadLocal的 部分源码分析

  • ThreadLocal在项目中的使用

ThreadLocal简介

每个线程都包含对其本地线程副本的隐式引用变量,只要线程处于活动状态,实例是可访问的;

线程消失后,它的所有副本线程本地实例接受垃圾收集(除非存在对这些副本的其他引用)。

简单理解为每个线程副本,每个线程都有自己的副本,相互之间并不知晓。

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获取用权限信息做权限校验等。


标签:String,再谈,value,ThreadLocal,static,线程,public
来源: https://blog.51cto.com/10983206/2563661

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有