ThreadLocal
1.定义
Java中,ThreadLocal用于创建线程局部变量,即每个线程都有自己独立的变量副本,每个线程都可以独立的访问自己的副本,互不干扰。一般存储线程的私有数据。
ThreadLocal类中主要方法包括:
set(T value);设置当前线程的局部变量值
get();获取当前线程的局部变量值
remove();移除当前线程的局部变量值
2.部分源码分析
public static void main(String[] args) {
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("string");
}
实例化ThreadLocal的时候,可以指定泛型,set(T value)方法传入的就是指定泛型类型的值
set(T value)的源码如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
可以看到我们会获取当前线程,用来干嘛呢?我们会去获取一个ThreadLocalMap,这个ThreadLocalMap是什么呢?继续看这个getMap的源码
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
发现其实拿的就是当前线程的一个属性
ThreadLocal.ThreadLocalMap threadLocals = null;
那我们继续看下这个当前线程的这个ThreadLocalMap的源代码
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
看到了什么,Entry对象继承了一个弱引用对象?
- 先说下Entry的基本概念
Entry其实是Map接口里面的一个内部接口,他表示Map里面的一个键值对(key-value pair)。在java里面,Map表示键值对的集合,Entry表示单个键值对的实体。
那ThreadLocalMap中的Entry呢?其实也是键值对,只不过是ThreadLocalMap自定义的内部类。
- 再来说一下弱引用
弱引用描述的是有用但非必须的一些对象,当这个对象只被弱引用指向的时候,即便是内存足够的情况下,这个对象也会立即被垃圾回收器回收。在Java中使用WeakReference类来实现弱引用,WeakReference类包装了一个对象的弱引用,可以通过get()方法获取被弱引用指向的对象。当被弱引用指向的对象没有强引用时,垃圾回收器会回收该对象,并且get()方法会返回null。
举个栗子
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
这段代码怎么指向的呢?有三个指向如下:
- weakRef 强强的指向了 WeakReference对象
- WeakReference的泛型变量T reference 弱弱的指向了Object对象
- obj强强的指向了Object对象
然后我们进行下面的操作
obj = null; // 原始对象不再有强引用
此时会发生什么?
// 在某个时刻,可能会被垃圾回收器回收
Object retrievedObj = weakRef.get();
if (retrievedObj == null) {
System.out.println("Original object has been garbage collected");
}
好了,弱引用应该差不多了了解了,言归正传
- 我们结合上面的知识,再来看看ThreadLocalMap的源代码
Entry类继承了弱引用WeakReference类,这个WeakReference类包装了ThreadLocal的弱引用,然后来看下Entry的构造函数,super(k);
k是什么,还记得吗?也是ThreadLocal类型的参数啊;说明这个参数现在被弱弱的引用了。
继续回到set(T value);源码
map.set(this, value);这个this是什么,这个this就是ThreadLocal对象,所以这个Map的key就是ThreadLocal对象。
所以是不是这个ThreadLocal对象被两个引用指向了,一个强(new ThreadLocal),一个弱。
- 问题1:有人会问为什么不能都强强的引用?
举个例子啊,我现在把tl == null,会发生什么,ThreadLocal对象强引用没了,只剩下弱引用了,那么就可以被垃圾回收期回收了,如果是强引用,那不就是内存泄露了吗?
- 这时候,又有人问,那ThreadLocalMap中的Entry对象仍然存在怎么办呢?
当ThreadLocalMap在进行操作时会检查Entry对象的key是否为null,如果为null,则会清理掉这个Entry对象。以便释放被回收的ThreadLocal对象所占用的内存。这种设计可以帮助避免内存泄漏,因为即使ThreadLocal对象被回收,Entry对象也会被及时清理掉。
但是呢,如果这个线程生命周期很长,这个ThreadLocal对象一直被频繁占用,可能会导致大量的数据堆积在ThreadLocalMap中,直到线程结束。
如果使用完ThreadLocal,我们没有调用remove()方法手动清理,这些值也会一直存在,直到在某个适当的时候被清理,也就是上面说的ThreadLocalMap操作的时候会检查key为null的数据
- 那又有人问,我每次都调用remove()不就好了,还设计什么弱引用?
那emm就是框架的设计肯定需要考虑多个方面,实际开发会存在遗漏吗,就需要一个健壮强大的框架。