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是我們存儲的對象)
發佈了164 篇原創文章 · 獲贊 114 · 訪問量 69萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章