首先,分析下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<Long> 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是我們存儲的對象)