概述
1.ThreadLocal叫做“线程变量”,其中填充的变量属于当前线程,对其他线程是隔离的,不会被别的线程读取或修改。这种思路叫线程封闭。
原理是,每个线程内部都有一个ThreadLocalMap,它用ThreadLocal作为键,能够存储值。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal适合,每个线程需要有自己的实例,且该实例需要在多个方法间传递,但不希望线程间共享。
实例需要在多个方法间传递,就可以保存在当前线程的ThreadLocal中,就不需要通过参数传递了。需要时直接get()取出。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
测试Demo
1.模拟用户登录后的业务操作,新建用户基本信息类
@Data
public class UserDto {
private String userName;
private String pwd;
private String userId;
// private byte[] info=new byte[1024*1024];
}
2.新建拦截器MyIntercepter
public class MyIntercepter implements HandlerInterceptor {
/**
* 处理请求前
* @param request
* @param response
* @param handler
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
log.info("处理请求前");
//模拟用户登录获取用户信息
UserDto user = new UserDto();
user.setUserName("张三");
user.setPwd("1234");
user.setUserId("222");
UserLocalUtil.setUser(user);
return true;
}
/**
* 处理请求
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
log.info("处理请求");
}
/**
* 处理请求后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
//UserLocalUtil.clear();
log.info("处理请求后");
}
3.添加拦截器配置
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyIntercepter()).addPathPatterns("/**")
.excludePathPatterns("**/user/login");
}
}
4.新建测试controller
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@GetMapping("/getUser")
public UserDto login(){
log.info("开始执行方法");
return UserLocalUtil.getUser();
}
}
5.测试请求http://localhost:8001/user/getUser
使用多线程改变user对象的属性后进行测试
@GetMapping("/getUser")
public UserDto login(){
log.info("开始执行方法");
new Thread(new Runnable() {
@Override
public void run() {
UserDto userDto = new UserDto();
userDto.setUserName("测试");
userDto.setPwd("111");
UserLocalUtil.setUser(userDto);
}
}).start();
return UserLocalUtil.getUser();
}
继续请求后发现返回的数据依旧没有改变,证实了ThreadLocal的数据是线程内共享,线程间隔离。与之有类似的还有synchronized锁,该锁是线程间共享,保证该关键字修饰的代码块或者方法只能有一个线程进行访问。
注意事项
1.使用完ThreadLocal的数据后要进行清理,不然随着使用次数的增加,会导致系统内存溢出的风险。
由于强引用的对象是不会被GC回收,所以存在内存溢出的风险,因此在使用完ThreadLocal后调用remove方法进行清除。
2.测试
设置启动参数,修改堆大小。-D -Xms20m -Xmx20m -Xmn20m。给user对象里增加字节对象,多次请求后控制台报错。
总结
1.应用场景
(1):用作保存每个线程独享的对象,为每个线程都创建一个副本,每个线程都只能修改自己所拥有的副本, 而不会影响其他线程的副本,在一定程度上让线程不安全的操作变得安全
(2):用作每个线程内需要独立保存信息的场景,供其他方法更方便得获取该信息,避免了方法之间的传参。
2.注意事项:使用完后调用remove方法进行清除,以免造成内存溢出。
有写的不对的地方欢迎指点,感谢。