ThreadLocal 叫做本地线程变量,意思是说,ThreadLocal 中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal 为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
一、ThreadLocal初探
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class ThreadLocalTest {
static ThreadLocal<String> localVar = new ThreadLocal<>();
static void print(String str) { System.out.println(str + " :" + localVar.get()); localVar.remove(); }
public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { localVar.set("localVar1"); print("thread1"); System.out.println("after remove : " + localVar.get()); } });
Thread t2 = new Thread(new Runnable() { @Override public void run() { localVar.set("localVar2"); print("thread2"); System.out.println("after remove : " + localVar.get()); } });
t1.start(); t2.start(); } }
|
运行结果如下
1 2 3 4
| thread2 :localVar2 thread1 :localVar1 after remove : null after remove : null
|
由此可见,各线程的 ThreadLocal 是互不相关的。
实际上,可以把ThreadLocal看成一个全局Map<Thread, Object>:每个线程获取ThreadLocal变量时,总是使用Thread自身作为key:
1
| Object threadLocalValue = threadLocalMap.get(Thread.currentThread());
|
因此,ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰。
二、web项目ThreadLocal保存用户信息
项目概述:
1 2 3 4
| 在登录代码中,当用户登录成功时,生成一个登录凭证存储到redis中, 将凭证中的字符串保存在cookie中返回给客户端。 使用一个拦截器拦截请求,从cookie中获取凭证字符串与redis中的凭证进行匹配,获取用户信息, 将用户信息存储到ThreadLocal中,在本次请求中持有用户信息,即可在后续操作中使用到用户信息。
|
使用ThreadLocal替代Session完成保存用户登录信息功能,这样既可以在同一线程中很方便的获取用户信息,又不需要频繁的传递session对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @Component public class LoginInterceptor implements HandlerInterceptor {
@Autowired private LoginService loginService;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)){ return true; } String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token)){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg()); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } SysUser sysUser = loginService.checkToken(token); if (sysUser == null){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg()); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } UserThreadLocal.put(sysUser); return true; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserThreadLocal.remove(); } }
|
三、ThreadLocal内存泄漏

实线代表强引用,虚线代表弱引用
每一个Thread维护一个ThreadLocalMap
其中key为指向ThreadLocal的一个弱引用,value为线程变量的副本。
如果一个ThreadLocal不存在外部强引用时(线程结束),Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key==null, 而value还存在着强引用,只有thread线程退出以后,value的强引用链条才会断掉。
重点来了,key 为什么被设计为弱引用?
参考链接