首先,分析下ThreadLocal的源码:
在分析ThreadLocal的具体用法前,我们来看下ThreadLocal对外提供的三个方法(set、get、delete)的源码:
1)set方法 设置变量
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
分析:
可以看到,当某个线程在具体调用ThreadLocal的set方法时,除了设置要保存的自身变量外,还保存了当前线程的信息(this)。
2)get方法 获取变量
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
分析:
map.getEntry(this)获取当前线程this的信息,并把相应的值取出来。
3)remove方法 移除变量
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
分析:
一般来说一次使用完之后,要把相应的值去除掉,防止内存泄漏。
其次,在实际项目中应用:比如:保存本次请求用户的信息、logId或者一些需要在一次request时需要都用的数据:
举个栗子如下:
1、在
uitls包下,定义一个
ThreadLocalUtil.java类:
public class ThreadLocalUtil {
private final static ThreadLocal threadLocal = new ThreadLocal<>();
public static void add(Long id) {
threadLocal.set(id);
}
public static Long getId() {
return threadLocal.get();
}
public static void remove() {
threadLocal.remove();
}
}
2、在包filter下,定义一个过滤器HttpFilter.java类:
@Slf4j
public class HttpFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest reqeust = (HttpServletRequest)servletRequest;
log.info("do filter, {}, {}", Thread.currentThread().getId(), reqeust.getServletPath());
ThreadLocalUtil.add(Thread.currentThread().getId());
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
3、在SpringBootDemoApplication.java中,配置过滤器:
@Bean
public FilterRegistrationBean httpFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new HttpFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
4、在包interceptor下,添加拦截器HttpIntercrptor.java类:
@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.info("preHandle");
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
ThreadLocalUtil.remove();
log.info("afterCompletion");
}
}
5、在包config下配置拦截器InterceptorConfig.java类如下:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");
}
}
6、编写测试控制层如下:
@GetMapping("/test4")
public String getTest4() {
return ThreadLocalUtil.getId().toString();
}
分析:
一般在Java web中使用ThreadLocal时,在过滤器filter中设置set相应的值,然后,为了防止内存泄漏,在拦截器的afterComletion中进行remove移除相应的变量值。
补充知识点:
线程封闭
什么是线程封闭:
把对象封装到一个线程中,只有这个线程能看到这个对象,那么这个对象就算不是线程安全的,也不会出现线程安全的问题。
实现线程封闭的方法:
1)Ad-hoc 线程封闭:程序控制实现,最糟糕,忽略
2)堆栈封闭:局部变量,无并发问题。(即:多个线程访问一个方法中的局部变量,都会把局部变量拷贝一份到线程的堆栈中)
3)ThreadLocal线程封闭:特别好的封闭方法 (ThreadLocal中维护了一个map,map的key是线程的名称,value是我们存储的对象)