ThreadLocal 叫做线程变量,意思是 ThreadLocal 中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLocal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。
ThreadLocal 变量通常被 private static 修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
下图可以增强理解:
ThreadLocal
但是 ThreadLocal 和 Synchorized 有本质的区别:
而 Synchorized 却正好相反,它用于在多个线程间通信时能够获得数据共享。
一句话理解 ThreadLocal ,threadLocal 是作为当前线程中属性 ThreadLocalMap 集合中的某一个 Entry 的 key 值,Entry(threadlocal,value),虽然不同的线程之间 threadLocal 这个 key 值是一样,但是不同的线程所拥有的 ThreadLocalMap 是独一无二的,也就是不同的线程间同一个 ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个 value 变量地址是一样的。
public class ThreadLocalTest {
private static ThreadLocal localVar = new ThreadLocal();
static void print(String str) {
// 打印当前线程中本地内存中变量的值
System.out.println(str + ":" + localVar.get());
// 清除内存中的本地变量
localVar.remove();
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
ThreadLocalTest.localVar.set("xdclass_A");
print("A");
// 打印本地变量
System.out.println("清楚后:" + localVar.get());
}
}, "A").start();
Thread.sleep(1000);
new Thread(new Runnable() {
@Override
public void run() {
ThreadLocalTest.localVar.set("xdclass_B");
print("B");
// 打印本地变量
System.out.println("清楚后:" + localVar.get());
}
}, "B").start();
}
}
A:xdclass_A
清楚后:null
B:xdclass_B
清楚后:null
从这个示例中我们可以看到,两个线程分别获取了自己线程存放的变量,他们之间变量的获取并不会错乱。这个的理解也可以结合图,相信会有一个更深刻地理解。
ThreadLocal 适用于如下两种场景
对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。
对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。
@Slf4j
public class OnlineUserUtil {
private final static ThreadLocal threadLocal = new ThreadLocal<>();
public static void set(UserInfo userInfo) {
threadLocal.set(userInfo);
}
public static UserInfo get() {
return threadLocal.get();
}
public static void remove() {
threadLocal.remove();
}
}
@Slf4j
@Aspect
@Component
@Order(2)
public class TokenAuthenticationAspect {
@Before(value = "@annotation(tokenAuthentication)")
public void doBefore(JoinPoint pjp, TokenAuthentication tokenAuthentication) {
// 校验代码
log.info("验证成功,保存到threadLocal userInfo={}", userInfo);
OnlineUserUtil.set(userInfo);
}
@AfterReturning(value = "@annotation(tokenAuthentication)")
public void doAfter(TokenAuthentication tokenAuthentication) {
OnlineUserUtil.remove();
}
}
这样在方法中,都能用到 userInfo 这个对象。