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服务器tomcat等内部都持有一个ThreadPool 线程已经创建好了
特别想登陆这样的功能,不能只是往ThreadLocal#set 线程是复用的第二个请求也会执行ThreadLoca#set
如果不进行清理的话,Thread.threadLocal这个map中的键值对会越来越多导致内存泄漏 OOM
在合适的地方一定执行clear操作