17:Thread_Local
17.1、什么是Thread_Local
- **ThreadLocal 实际上是一个容器,它可以存储线程本地的变量,并且保证每个线程之间互不干扰。**每个线程都拥有自己的 ThreadLocal 实例,它可以在其中存储数据,并且这些数据对其他线程是不可见的。ThreadLocal 的主要作用是提供线程内部的局部变量,可以将某个对象绑定到当前线程,然后在线程的任何地方都可以通过 ThreadLocal 获取到绑定的对象,而不需要通过方法参数传递。这样可以方便地在多个方法之间共享数据,同时又保证了线程安全。
17.2、demo
/**
* ThreadLocal初认识
* @author xxl
* @since 2023/6/22
*/
public class Test47_ThreadLocal {
public static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("hello,world");
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(()->{
//打印主线线程存入的信息
String info = (String) threadLocal.get();
System.out.println(info);
});
String info = (String) threadLocal.get();
System.out.println(info);
}
}
//最终结果
//hello,world
//null
17.3、底层解密
-
所有的对象都是存在 JVM 的堆区中,可以被所有线程访问到。但是,ThreadLocal 的实现机制可以使得每个线程拥有自己独立的变量副本,其他线程无法直接访问到该副本。
-
在 ThreadLocal 的底层实现中,每个 ThreadLocal 实例都关联了一个 ThreadLocalMap 对象,其中存储了线程独立的变量副本。ThreadLocalMap 是一个自定义的 Map 实现,它的键是 ThreadLocal 实例,值是对应线程的变量副本。
-
当通过 ThreadLocal 的 set 方法设置变量时,实际上是将变量存储在当前线程的 ThreadLocalMap 中,键为 ThreadLocal 实例。而通过 get 方法获取变量时,会根据当前线程获取对应的 ThreadLocalMap,然后根据 ThreadLocal 实例作为键获取到相应的变量副本。
-
由于每个线程都有自己的 ThreadLocalMap,因此可以保证线程之间的数据互不干扰。不同线程之间访问的是各自的 ThreadLocalMap,而不会直接访问其他线程的变量副本。
-
这种机制在源码中的具体实现是通过线程的 ThreadLocal.ThreadLocalMap 字段实现的。每个线程在 Thread 类中都有一个名为 threadLocals 的字段,它是一个 ThreadLocal.ThreadLocalMap 类型的实例。通过该字段,可以实现线程级别的变量隔离。
-
ThreadLocal 的设计思路是基于线程封闭性的原则,即通过将变量与线程绑定,避免了多线程并发访问共享数据的问题。这样可以简化代码的编写,提高程序的并发性和线程安全性。
-
需要注意的是,虽然每个线程独立拥有自己的变量副本,但并不意味着变量副本的生命周期与线程完全一致。当线程结束时,如果没有及时清理 ThreadLocalMap 中的变量副本,可能会导致内存泄漏问题。因此,在使用 ThreadLocal 时要注意适时清理和释放资源,避免潜在的问题。
结合代码
1.set方法
public void set(T value) {
set(Thread.currentThread(), value);
}
private void set(Thread t, T value) {
//ThreadLocal静态内部类,访问控制为default,只有同包下可以访问
ThreadLocalMap map = getMap(t);
//这一步是判断map是否合法
if (map == ThreadLocalMap.NOT_SUPPORTED) {
throw new UnsupportedOperationException();
}
if (map != null) {
//当前Thread_Local对象为键
map.set(this, value);
} else {
createMap(t, value);
}
}
//每个线程都有一个threadLocals也就是ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
2.get方法
public T get() {
return get(Thread.currentThread());
}
private T get(Thread t) {
//通过当前线程获取绑定的Map
ThreadLocalMap map = getMap(t);
if (map != null) {
if (map == ThreadLocalMap.NOT_SUPPORTED) {
return initialValue();
} else {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
}
return setInitialValue(t);
}
总结:以上存储过程没有详细,因为跟map差不多,有兴趣可以看看其实Thread_Local有点像session一样,实际作用就是相当于一个简易缓存
17.4、常见应用场景
1)以前前后端不分离存储用户信息大概率放在session中,分离之后要么存在缓存或者redis。现在多了一种方式:具体就是在spring boot使用拦截器获取token唯一的id查询出用户信息存在Thread_Local中,在拦截器afterCompletion方法中remove掉做到不泄露内存
2)数据库连接管理:在使用数据库连接池时,每个线程需要获取自己独立的数据库连接。可以使用 ThreadLocal 来管理线程的数据库连接,确保每个线程获取到的是自己的连接,避免了线程安全问题。
。。。。