你们对于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获取用权限信息作权限校验等。