java并发编程--ThreadLocal

  • 什么是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;
	}
	
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值