ThreadLocal是java语言中jdk的一个类包,它的主要作用就是为线程提供本地变量,存储于线程内部的一些变量。因此ThreadLocal就不存在多线程共享变量产生的安全问题,也就是说它是线程安全的。所以我们基于此进行线程上下文的存储就是天然的优势了。
我们做一个简单的demo测试。声明一个静态的threadLocal。
在main方法中,启动两个线程,线程1先set值为100,然后sleep 2秒。线程2先sleep 1秒然后set值为200.
这样的目的是确保线程1先赋值后,线程2再赋值,此时打印线程2值为200,线程1值为100 。
这也就说明了,虽然线程2后赋值,但没有覆盖线程1的值,也就是说明2个线程的值互相不干扰。
public class ThreadLocalDemo {
// 定义一个ThreadLocal变量
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 创建并启动两个线程
Thread thread1 = new Thread(() -> {
// 线程1设置ThreadLocal变量的值
threadLocal.set(100);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 线程1获取并打印ThreadLocal变量的值
System.out.println("Thread1: " + threadLocal.get());
});
Thread thread2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 线程2设置ThreadLocal变量的值
threadLocal.set(200);
// 线程2获取并打印ThreadLocal变量的值
System.out.println("Thread2: " + threadLocal.get());
});
thread1.start();
thread2.start();
}
}
打印结果
我们通过get来看一下ThreadLocal内部到底如何实现的:
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果ThreadLocalMap不为空
if (map != null) {
// 在ThreadLocalMap中查找当前ThreadLocal对象对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果Entry不为空
if (e != null) {
// 将Entry的value转换为泛型类型T,并返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果ThreadLocalMap为空或者Entry为空,则设置初始值并返回
return setInitialValue();
}
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
基于上面的逻辑,我们可以看一下关系图
我们继续看一下初始化值:
到这里,get方法就结束了。
private T setInitialValue() {
// 调用initialValue方法获取初始值
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果ThreadLocalMap不为空
if (map != null) {
// 在ThreadLocalMap中设置当前ThreadLocal对象对应的值
map.set(this, value);
} else {
// 如果ThreadLocalMap为空,则创建一个新的ThreadLocalMap,并设置值
createMap(t, value);
}
// 如果当前ThreadLocal对象是TerminatingThreadLocal的实例
if (this instanceof TerminatingThreadLocal) {
// 将其注册到TerminatingThreadLocal的管理器中
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
// 返回初始值
return value;
}
set我们就简单看下源码就明白了,其实上面的初始化方法也就是set方法的执行流程,只是数据是通过参数传递进来的。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
虽然ThreadLocal很有用,但是实际使用起来并不复杂。
我们首先创建一个ThreadLocal存储当前线程的上下文信息,然后提供一个set方法和一个get方法,方便存储和获取。
注意,这个clear方法是必须要的:再线程池的情况下需要手动清除,不然线程实例重复获取数据会重复。
public class UserAuthContext {
/**
* 用户的登录相关的ThreadLocal
*/
private static ThreadLocal<UserDO> userLoginInfo = new ThreadLocal<>();
public static void setUserLoginInfo(UserDO userInfo) {
userLoginInfo.set(userInfo);
}
/**
* 获取用户登陆相关信息
*/
public static UserDO getUserInfo() {
return userLoginInfo.get();
}
/**
* 清空用户登陆相关信息
*/
public static void clear(){
userLoginInfo.remove();
}
}
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//验证、鉴权
//获取到用户信息, 存储用户信息到上下文
UserAuthContext.setUserLoginInfo(userInfo);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
}
在实际需要获取用户信息的地方,使用这个方法直接获取线程中存放的用户信息
//获取登录用户信息
UserDO userDo = UserAuthContext.getUserInfo();
总的来说ThreadLocal是线程安全的,所以使用起来很方便,不需要考虑共享变量产生的安全问题了。其次ThreadLocal已经封装好了基础的使用方法,所以代码相当简洁。最后我们在实战中看到,它很好的解决了上下文中数据传递的问题。
当然使用ThreadLocal也是有风险的,可能会存在内存泄露,这是因为他在Entry中存储是弱引用的,但对应的值是强引用,也就是说当ThreadLocal对象被回收后,其对应的值可能仍然存在于ThreadLocalMap中,无法被垃圾回收器回收。如果线程长时间运行,不断创建和使用ThreadLocal,就可能会占用大量内存,最终导致OutOfMemoryError。
所以在使用ThreadLocal的时候,上方我提到的remove方法一定要设计合理。否则有可能存在值覆盖及泄露问题。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。