ThreadLocal的使用
引言
在高并发的情况下,线程安全是尤其重要的,其中线程安全又分为多个方面,安全发布对象、不可变对象、线程封闭等。其中,线程封闭就是将变量封装到一个线程中, 这样并发的其他线程就无法看到和使用该变量,这样就保证了线程的安全性。而线程封闭的其中一种就是使用ThreadLocal,这在我们实际开发中也是非常有用的。
ThreadLocal介绍
ThreadLocal主要是通过以Map的形式对数据进行存储,其中 key为当前线程,而value值是我们传入的参数;使用ThreadLocal可以让我们的代码更简洁,并且在想使用我们存储数据的任何地方都可以拿到。最重要的是它是线程封闭的,也就是线程安全的,适用于在多线程场合。
ThreadLocal部分源码
ThreadLocal最常用的3个方法,一、设置值,二、取出值,三、清空值(请及时清空,以免发生内存溢出的情况)
设置值
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
取出值
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(); }
清空值
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
使用场景
通常情况下,都是结合过滤器和拦截器进行使用,在过滤器时存值,然后在接口执行结束时清空值
实例
以springboot中使用ThreadLocal为例
定义ThreadLocal操作类
1 package com.me.concurrency.threadlocal;
2
3 public class RequestHolder {
4
5 private static ThreadLocal<Long> threadId = new ThreadLocal<>();
6
7 public static void add(Long id) {
8 threadId.set(id);
9 }
10
11 public static void remove() {
12 threadId.remove();
13 }
14
15 public static Long get() {
16 return threadId.get();
17 }
18
19
20 }
定义过滤器
1 package com.me.concurrency.threadlocal; 2 3 import lombok.extern.slf4j.Slf4j; 4 import org.springframework.stereotype.Component; 5 6 import javax.servlet.*; 7 import javax.servlet.http.HttpServletRequest; 8 import java.io.IOException; 9 10 @Slf4j 11 public class HttpFilter implements Filter { 12 @Override 13 public void init(FilterConfig filterConfig) throws ServletException { 14 15 } 16 17 @Override 18 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 19 HttpServletRequest request = (HttpServletRequest) servletRequest; 20 log.info("currentThreadId --{}", Thread.currentThread().getId()); 21 log.info("the request url is:{}", request.getRequestURL()); 22 RequestHolder.add(Thread.currentThread().getId()); 23 filterChain.doFilter(servletRequest, servletResponse); 24 } 25 26 @Override 27 public void destroy() { 28 29 } 30 }
定义拦截器
1 package com.me.concurrency.threadlocal; 2 3 import lombok.extern.slf4j.Slf4j; 4 import org.springframework.web.servlet.ModelAndView; 5 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 6 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 @Slf4j 11 public class CustomInterceptor extends HandlerInterceptorAdapter { 12 13 @Override 14 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 15 return true; 16 } 17 18 // 正常结束 19 @Override 20 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 21 super.postHandle(request, response, handler, modelAndView); 22 } 23 24 // 正常与异常结束 25 @Override 26 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 27 log.info("interceptor compeleted.."); 28 RequestHolder.remove(); 29 } 30 31 32 }
springboot中过滤器和拦截器的配置
1 package com.me.concurrency.config; 2 3 import com.me.concurrency.threadlocal.CustomInterceptor; 4 import com.me.concurrency.threadlocal.HttpFilter; 5 import org.springframework.boot.web.servlet.FilterRegistrationBean; 6 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Configuration; 8 import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 9 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 10 11 @Configuration 12 public class WebMvcConfig extends WebMvcConfigurerAdapter { 13 14 15 @Bean 16 public FilterRegistrationBean httpFilter() { 17 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); 18 filterRegistrationBean.setFilter(new HttpFilter()); 19 filterRegistrationBean.addUrlPatterns("/threadLocal/*"); // 过滤的请求 20 return filterRegistrationBean; 21 } 22 23 @Override 24 public void addInterceptors(InterceptorRegistry registry) { 25 registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/threadLocal/**"); //拦截的请求 26 } 27 }
测试接口
@RequestMapping("/threadLocal/getId") @ResponseBody public Long threadLocal(){ return RequestHolder.get(); }
执行结果
写在最后
ThreadLocal在项目中的使用非常常见,它能将我们需要存储的值以key为当前线程,value为实际存储值的形式存储在map的数据结构中,并且对存储的值进行了线程封闭,其他线程是不能看到和使用的,所以是线程安全的,适用于多线程,高并发的场景下;而且,使用ThreadLocal可以简化我们的代码,我们不需要将存储值一层一层的传递下去,而是在任何地方直接通过ThreadLocal取到值。