ThreadLocal源码分析

ThreadLocal源码分析

常见使用场景

ThreadLocal使用的常见场景

1)登录用户信息的存放 UserContext持有一个ThreadLocal
2)框架中 事务需要将Connection放入ThreadLocal 保证多个(dao或者Service操作)被外层的Service的时候使用同一个Connection达到事务效果
3)Spring Cloud的Sleuth存放Span,TraceId等

总体来说就是同一个线程内,可以用ThreadLocal来传递变量,而无需额外的方法参数。

具体使用

public class LoginMemberContext {
    private LoginMemberContext(){}
    private static ThreadLocal<LoginMemberModel> loginThreadLocal = new ThreadLocal<>();
    
    public static void set(LoginMemberModel loginMemberModel){
        loginThreadLocal.set(loginMemberModel);
    }
    public static LoginMemberModel get(){
        return loginThreadLocal.get();
    }
    public static void clear(){
        loginThreadLocal.remove();
    }
}    

登录拦截器塞入ThreadLocal

public class LoginHandlerInterceptor implements HandlerInterceptor {
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
	    String token = request.getHeader("token");
		LoginMemberModel loginMemberModel = loginMemberRService.checkLoginedByToken(token);
		if(loginMemberModel == null){//flush出去
            writeFlushNotAuthFlag(request);
            return false;
		}
		loginMemberModel.setToken(token);
		LoginMemberContext.set(loginMemberModel);
   }
}

在Controller中使用

@RestController
@RequestMapping("/right")
public class RightIndexController {
	@GetMapping("/{cityCode}/index")
    public ApiResult<Object> index(@PathVariable String cityCode){
        LoginMemberModel loginMemberModel = LoginMemberContext.get();//这样就能获取存储的用户信息了
        String alipayUserId = loginMemberMode.getAlipayUserId();
        //此处省略 很多字
        return ApiResult.buildSuccess();
    }
}

理解变量和线程绑定

如何理解变量和线程绑定
不看源码第一印象可能就是
ThreadLocal中有一个Map成员 set就是讲Thread作为key value就是value哈!!!有没有中枪??然并卵。

源码分析

public class ThreadLocal<T> {

	//静态内部类  但却是Thread引用它 后边会说明
    static class ThreadLocalMap {
    	//类似于HashMap
    }

   public void set(T value) {...}
   public T get() {...}
   void createMap(Thread t, T firstValue){..}
   static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap){..}
}

内有有一个内部类 ThreadLocalMap 类似于Map结构
方法内部去没有直接使用被Thread类持有(引用)

几个核心方法

ThreadLocal#set方法【使用的是Thread.threadLocal属性】

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//就是 t.threadLocals;
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);//就是 t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //可以被子类重写!!!
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

set方法是指就是 初始化Thread.threadLocal成员 它是ThreadLocal的内部类ThreadLocalMap 类似于Map
塞入Thread的成员threadLocal这个map 键值对 key为ThreadLocal自身,value为变量
这样一来线程Thread就将 ThreadLocal和变量放入到自己的成员中 所以安全了

因为每个线程都有一个ThreadLocal的内部类ThreadLocalMap 所以安全

估计这点跟大家想的不太一样,ThreadLocal里边的map 是被线程持有 当调用TheadLocal#set方法的时候
实际是往Thread.threadLocal这个map中塞入的值 key是ThreadLoca本身 value是变量

常见问题

子线程不能获取到当前线程设置的变量

方案就是使用 ThreadLocal的子类 InheritableThreadLocal

再次之前看下Thread类的源码
线程Thread类 持有ThreadLocal的内部类ThreadLocalMap

public class Thread implements Runnable {

    ThreadLocal.ThreadLocalMap threadLocals = null;//线程持有TheadLocal的内部类
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

	public Thread(){
		init(..){...}//这里暂时已列出来  这个跟第二个成员 inheritableThreadLocals有关系
	} 
   //在否早方法中执行的 也就是创建子线程的时候拷贝Thread的第二个成员到子线程的第二个成员 有什么用 后边说!
   init(xxx){
         Thread parent = currentThread();
		if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
	}   
}

线程持有内部有两个成员 都是ThreadLocal的内部类ThreadLocalMap 先看第一个
构造方法内部 (也就是创建子线程的时候),将Thread的第二个成员进行复制给子线程 用处后边会说!!

在回忆之前的ThreadLocal#set方法

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//就是 t.threadLocals;
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);//就是 t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //可以被子类重写!!!
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

实际上InheritableThreadLocal将getMap和createMap重写了

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

总结下:

ThreadLocal#set(val)实际是在Thread.threadLocal(第一个ThreadLocal的内部类ThreadLocalMap 成员)这个map 放置key=this value=val的键值对
InheritableLocal#set(val) 实际在Thread.inheritableThreadLocal(第二个ThreadLocal的内部类ThreadLocalMap 成员)这个map 放置key=this value=val的键值对
创建子线程的时候即new Thread()构造方法内部 将Thread.inheritableThreadLocal成员复制了一份到子线程 ,所以子线程能获取到父线程的成员了

web线程池中使用注意事项

web应用像web服务器tomcat等内部都持有一个ThreadPool 线程已经创建好了
特别想登陆这样的功能,不能只是往ThreadLocal#set 线程是复用的第二个请求也会执行ThreadLoca#set
如果不进行清理的话,Thread.threadLocal这个map中的键值对会越来越多导致内存泄漏 OOM

在合适的地方一定执行clear操作

自定义线程池如何获取ThreadLocal中的变量值

你可能感兴趣的:(技术博客,JUC并发包,Java并发包JUC-源码解读)