ThreadLocal 用法
ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
使用场景:
1. 在进行对象跨层传递的时候,使用threadLocal可以避免多次传递,打破层次间的约束。
2. 线程间数据隔离。
3. 进行事务操作,用于存储线程事务信息。
4. 数据库连接,Session会话管理。(spring框架在事务开始时会给当前线程绑定一个JDBC Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是ThreadLocal来实现的)
ThreadLocal 代码示例
示例业务场景:用于存放用户信息,以便后续查询业务中获取用户信息
1. 定一个UserManager类
public class UserManager {
private static ThreadLocal<User> userLocal = new ThreadLocal<User>();
public static User getUser() {
return userLocal.get();
}
public static void setUser(User user) {
userLocal.set(user);
}
}
2. 定义一个拦截器每次请求来的时候存 将用户信息放入 ThreadLocal中
@Slf4j
public class DemoHttpInterceptor extends GenericFilterBean {
@Autowired
UserService userService;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
String usercode = request.getHeader("iv-user"); //前端传过来员工号
log.info("------>>>DemoHttpInterceptor " + usercode );
loginUser = userService.findUserByStaffcode(usercode ); //数据库中查找用户信息
UserManager.setUser(loginUser); // 将用户信息放入 ThreadLocal中
chain.doFilter(req, res);
}
}
3. 在业务代码中获取用户信息
@Slf4j
@RestController
@RequestMapping("/learning")
public class LearningInfoController {
@Autowired
private LearningInfoService learningInfoService;
@RequestMapping(method = RequestMethod.POST)
public RestResult<PdsLearninginfo> addLearningInfo(@Valid @RequestBody PdsLearninginfo learninginfoModel, BindingResult bindingResult) {
PdsUser loginUser = UserManager.getUser(); // 从ThreadLocal中获取用户信息
return RestResultGenerator.genSuccessResult(this.learningInfoService.save(learninginfoModel, loginUser));
}
}
ThreadLocal 数据结构设计
1. 每个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储在本线程中所有的ThreadLocal对象以及其对应的值;
2. ThreadLocalMap由一个个Entry对象构成,Entry继承自WeakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和Object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。
3. set方法,执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap中。
//JDK1.8 源码, java.lang.ThreadLocal
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 然后获取当前线程的ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
4. get方法,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
//JDK1.8 源码, java.lang.ThreadLocal
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 然后获取当前线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocal内存泄漏的原因,如何避免
内存泄漏:不再会被使用的对象占用的内存不能被回收。
ThreadLocal中主要的存储单元Entry类继承了WeakReference,弱引用即时没有手动删除,ThreadLocal也会被回收,但是对于value如果是强引用类型,就需要进行手动remove,避免value的内存泄露。
如果一个ThreadLocal不存在外部强引用时,key势必会被GC回收,这样就会导致ThreadLocalMap为null,而Value还存在强引用,只有这个线程退出后,value的强引用链才会断掉,如果线程迟迟不结束,这个key为null的value就会一直存在一条强引用链。
ThreadLocal的正确使用方式
1. 每次使用完ThreadLocal都调用它的remove()方法清除数据。
2. 将ThreadLocal变量定义成 private static, 这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的Value值,进而清除掉。