-
什么是ThreadLocal
1.先看一下jdk文档的定义
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
看一下翻译结果,重点有两句话:
(1).线程局部变量的作用是实现每个线程访问(通过get或set方法)自己独立的变量
(2).ThreadLocal
实例通常是私有的静态字段
2.如何理解每个线程访问自己独立的变量,先来看一段测试代码。
示例代码:
@Service
public class ThreadLocalTest1 {
public void test() {
InnerClass innerClass = new InnerClass(3);
innerClass.run();
new Thread(innerClass).start();
}
class InnerClass implements Runnable {
private ThreadLocal<Integer> num = new ThreadLocal<>();
public InnerClass(int num) {
System.out.println("内部类构造函数开始执行,当前线程名称:" + Thread.currentThread().getName());
this.num.set(num);
}
@Override
public void run() {
System.out.println("当前线程名称:" + Thread.currentThread().getName() + "局部变量值:" + num.get());
}
}
}
单元测试:
@Autowired
ThreadLocalTest1 threadLocalTest1;
@Test
public void threadLocalTest1() {
threadLocalTest1.test();
}
测试结果:
这个示例代码中,InnerClass对象在main线程中创建,访问时可以获取到这个值。启动子线程之后,执行同一个对象的run方法,可以调用run方法但是返回的是null,原因是在创建子线程的时候并没有对这个线程局部变量进行赋值。
3.为什么帮助文档说ThreadLocal通常是静态私有的
如果一个变量需要在一个线程中进行传输,一种方式是定义为对象的属性,在整个线程执行的过程中传递,这就不需要用到线程局部变量了。另外一种方式就是定义为静态变量,如果定义静态的ThreadLocal,就实现了为每个线程绑定一个变量,随时取用,不需要全流程传递的功能,即线程全局共享变量。
-
ThreadLocal变量的线程安全问题
使用List<String>来测试是否处理了变量的线程安全问题,从输出结果可以看出来,虽然两个线程在两个ThreadLocalMap中分别存储了值,但是引用对象还是指向了同样的地址,所以输出内容互相受影响。
示例代码:
public class ThreadLocalTest2 implements Runnable {
private static List<String> list = new ArrayList<>();
private static ThreadLocal<List<String>> listThreadLocal = ThreadLocal.withInitial(() -> list);
public static void main(String[] args) {
new Thread(new ThreadLocalTest2()).start();
new Thread(new ThreadLocalTest2()).start();
}
@Override
public void run() {
try {
list.add("线程名称:" + Thread.currentThread().getName());
listThreadLocal.set(list);
System.out.println("局部变量内容:" + listThreadLocal.get());
} finally {
list.clear();
listThreadLocal.remove();
}
}
}
输出结果:
总结一下,ThreadLocal并不是为了解决共享变量的线程安全问题,而是提供了变量和线程的绑定机制。
-
一个使用场景
下面例子实现了一个在拦截器中获取并保存ip、ua等通用信息的功能,因为在拦截器中处理好之后,必须使用静态变量来保存,才好解决数据的传输问题,但是还存在一个接口多个请求会修改静态变量的问题,所以这是使用ThreadLocal为每个访问的线程存储了一份接口请求的基本信息,便于后续需要的时候使用。
请求信息类代码:
public class RequestInfo {
private static ThreadLocal<Map<String,Object>> request = new ThreadLocal<Map<String,Object>>();
public static void putCurrentUserId(String userId){
put("userId", userId);
}
public static String getCurrentUserId(){
return (String)get("userId");
}
public static void removeCurrentUserId(){
Map<String,Object> requestContext = request.get();
requestContext.remove("userId");
}
public static void put(String key,Object value){
Map<String,Object> requestContext = request.get();
if(requestContext==null){
requestContext = new HashMap<String,Object>();
request.set(requestContext);
}
requestContext.put(key, value);
}
public static Object get(String key){
Map<String,Object> requestContext = request.get();
return requestContext==null?null:requestContext.get(key);
}
}
拦截器代码:
public class ClientInfoInterceptor extends HandlerInterceptorAdapter {
public static String TOKEN_UID="token_uid";
private final String USER_AGENT_TOKEN = "User-Agent";
public static String IP="ip";
public static String UA="ua";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
RequestInfo.put(IP, getIpAddr(request));
RequestInfo.put(UA, getUserAgent(request));
RequestInfo.put(TOKEN_UID, getUidCookie(request));
return true;
}
private String getUserAgent(HttpServletRequest request) {
String h = request.getHeader(USER_AGENT_TOKEN);
return h==null?"UNKNOW":h;
}
private String getIpAddr(HttpServletRequest request) {
String ip=request.getHeader("Cdn-Src-Ip");
if(isInvalidIp(ip)){
ip = request.getHeader("X-Forwarded-For");
}
if(isInvalidIp(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if(isInvalidIp(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if(isInvalidIp(ip)) {
ip = request.getRemoteAddr();
}
if (ip != null && ip.length() > 15 &&ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
return ip;
}
private boolean isInvalidIp(String ip) {
return ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip);
}
private String getUidCookie(HttpServletRequest request) {
return getCookie(TOKEN_UID, request.getCookies());
}
private String getCookie(String cookieName,Cookie[] cookies) {
if(cookies==null||cookies.length==0){
return null;
}
for(Cookie c:cookies){
if(cookieName.equals(c.getName())){
return c.getValue();
}
}
return null;
}
}