ThreadLocal是Java中的一个类,用于解决多线程环境下的并发问题。以下是对ThreadLocal的详细解释:
定义:
ThreadLocal,即线程局部变量,是Java提供的一种线程隔离的变量存储机制。每个线程都会有一个独立的ThreadLocal变量副本,这些副本之间互不干扰,从而实现线程间的数据隔离。
原理:
ThreadLocal内部维护了一个名为ThreadLocalMap的静态内部类,该Map的键是ThreadLocal对象本身(实际上是ThreadLocal的一个弱引用),值是线程变量的副本。每个线程都有一个自己的ThreadLocalMap实例,用于存储该线程独有的变量副本。因此,当线程访问ThreadLocal变量时,会通过自身的ThreadLocalMap获取对应的变量副本,从而保证了线程间的数据隔离。
- ThreadLocal的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);
}
}
简单来说,每个Thread线程都维护了一个ThreadLocalMap,当使用ThreadLocal对象存值时,会先根据当前线程找到ThreadLocalMap,这个ThreadLocalMap 的key是ThreadLocal对象,值就是ThreadLocal对象存储的值。因为一个线程运行期间会创建很多的ThreadLocal对象,所以这里是个Map结构。
- ThreadLocalMap
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
在ThreadLocalMap中key也就是ThreadLocal对象是弱引用的,当一个线程运行完成,如果栈内存或堆内存中没有强引用关联,则一定会被GC掉,但是value不会回收,value是和线程生命周期绑定的,线程不销毁,value也不会销毁。但是像Tomcat或者在业务系统中自定义的线程池都是会复用线程的,如果一直在创建ThreadLocal,则会导致内存泄漏,进而引发内存溢出。
- 脏读代码示例
一般的,ThreadLocal会和当前运行线程绑定,如果运行线程不销毁,且没有清理ThreadLocal中的值,则会产生脏读。
例如Tomcat每次请求来都会开一个线程处理,但是线程是被线程池管理的,执行完一次请求之后,线程会被复用,如果一个线程ThreadLocal没有及时清理,则下一个请求可能会由于业务逻辑判断错误读取到其他线程的值,我使用CompletableFuture对象来模拟实现一下。
- 定义一个数据值存储和查询对象
public class DataHandlerSafe<T> {
private ThreadLocal<T> data = new ThreadLocal<>();
public void setData(T t){
this.data.set(t);
}
public T