目录
入门
一、 什么是ThreadLocal?
ThreadLocal顾名思义,也叫做线程局部变量。
每个 Thread 线程内部都有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
既然每个 Thread 线程有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal是一种可以为每个线程提供变量副本的类,使得每个线程在某一时间访问到的并不是同一个对象。这样可以隔离多个线程对数据的共享,减少了线程同步所带来的性能消耗,也减少了线程并发控制的复杂度。
二、代码演示
使用场景:比如存放用户信息
import com.mszlu.blog.dao.pojo.SysUser;
public class UserThreadLocal {
private UserThreadLocal(){}
private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
public static void put(SysUser sysUser){
LOCAL.set(sysUser);
}
public static SysUser get(){
return LOCAL.get();
}
public static void remove(){
LOCAL.remove();
}
}
import com.alibaba.fastjson.JSON;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.ErrorCode;
import com.mszlu.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//在执行controller方法(Handler)之前进行执行
/**
* 1. 需要判断 请求的接口路径 是否为 HandlerMethod (controller方法)
* 2. 判断 token是否为空,如果为空 未登录
* 3. 如果token 不为空,登录验证 loginService checkToken
* 4. 如果认证成功 放行即可
*/
if (!(handler instanceof HandlerMethod)){
//handler 可能是 RequestResourceHandler springboot 程序 访问静态资源 默认去classpath下的static目录去查询
return true;
}
String token = request.getHeader("Authorization");
log.info("=================request start===========================");
String requestURI = request.getRequestURI();
log.info("request uri:{}",requestURI);
log.info("request method:{}",request.getMethod());
log.info("token:{}", token);
log.info("=================request end===========================");
if (StringUtils.isBlank(token)){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
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(), "未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
//登录验证成功,放行
//我希望在controller中 直接获取用户的信息 怎么获取?
UserThreadLocal.put(sysUser);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}
}
package com.mszlu.blog.controller;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("test")
public class TestController {
@RequestMapping
public Result test(){
// SysUser
SysUser sysUser = UserThreadLocal.get();
System.out.println(sysUser);
return Result.success(null);
}
}
三、ThreadLocal使用注意事项
- 使用ThreadLocal为避免空指针异常,则需要先初始化。因为它初始化时会先调用get()方法获取值,如果没有则会返回null值。
- 建议将ThreadLocal定义为static的,减少空间的浪费。
- 使用完一定记得remove()清除,避免内存泄漏。
四、内存泄漏
每一个Thread线程内都各自维护着一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。
所谓弱引用,其实就是我们的Java对象分为强、软、弱、虚四种引用。
强引用
比如我们通常new的这些对象,都是强引用对象。
它们都具有一个特点:当触发垃圾回收时,垃圾收集器会根据根可达算法判断当前对象是否存活,如果存活就不会进行垃圾回收。
也就是说只要当前对象一直存活就不会进行垃圾回收,即使出现OOM也不会。
软引用
软引用是说对象在内存满之前,都不会进行回收。内存满了即使它存活,也会被清除掉。
弱引用
只要一触发垃圾回收,不管当前对象是否存活,都会被回收掉。
那为什么TheadLocal内部TheadLocalMap的Entry对象的key是弱引用的ThreadLoal,还会出现内存泄漏呢?
当一个线程的生命周期进行到一段时间后,我们的key被回收掉了。(可能因为新生代满了等原因,垃圾回收触发了)
这时候,key没有了,但是value还存在,因为value是强引用的。
显然,key都没有了,肯定是获取不到这个value的。
当我们线程结束时,ThreadLocalMap也不会存活了,会伴随着这个ThreadLocalMap的回收。
但是这个map找不到了,所以这块内存就回收不了。
value就一致存在内存中了。
如果有大量的这种行为,那么就很可能造成内存泄露。
所以我们用完要手动删除掉这个ThreaLocal中的对象信息。