ThreadLocal在实际项目中的应用

首先,分析下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是我们存储的对象)

猜你喜欢

转载自blog.csdn.net/timchen525/article/details/80603188