1、大致意图
Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,类似HashMap;
1.1 类图
ThreadLocalMap key 就是 ThreadLocal 的引用,value就是我们要存的值(ThreadLocal 存储的内容);
1.2 example
public class ThreadLocalTest {
static ThreadLocal<String> localVar = new ThreadLocal<>();
static void print(String str) {
//打印当前线程中本地内存中本地变量的值
System.out.println(str + " :" + localVar.get());
//清除本地内存中的本地变量
localVar.remove();
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程1中本地变量的值
localVar.set("localVar1");
//调用打印方法
print("thread1");
//打印本地变量
System.out.println("after remove : " + localVar.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程1中本地变量的值
localVar.set("localVar2");
//调用打印方法
print("thread2");
//打印本地变量
System.out.println("after remove : " + localVar.get());
}
});
t1.start();
t2.start();
}
}
1.3 源码解析
1.3.1 set()
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap对象,即属性threadLocals
ThreadLocalMap map = getMap(t);
//如果map不为空,放入map;否则创建Thread的ThreadLocalMap实例
if (map != null)
map.set(this, value); //this指向当前调用的ThreadLocal对象,即ThreadLocalMap的key为ThreadLocal的弱引用
else
createMap(t, value);
}
1.3.2 getMap()
//返回线程的ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
1.3.3 createMap()
//创建ThreadLocalMap实例,并绑定给线程
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
看下ThreaLocalMap的constructor的逻辑
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化Entry数组
table = new Entry[INITIAL_CAPACITY];
//hash : key的hashCode & 数组的capacity-1
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//放入数组对应的位置
table[i] = new Entry(firstKey, firstValue);
//更新size,数组元素个数
size = 1;
//设置 扩容的加载因子
setThreshold(INITIAL_CAPACITY);
}
继续进去看Entry 的constructor的逻辑
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
调用的父类WeakReference的构造函数
public WeakReference(T referent) {
super(referent);
}
WeakReference再调用它父类Reference的构造函数
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
1.3.4 get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map不为空,根据ThreadLocal引用取出Entry
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
//若entry对象不为空,返回entry的value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//返回一个init value
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
1.3.5 remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
2、内存泄漏
首先我们先看看ThreadLocalMap的类图,在前面的介绍中,我们知道ThreadLocal只是一个工具类,他为用户提供get、set、remove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道threadLocals是一个ThreadLocalMap类型的变量,下面我们来看看ThreadLocalMap这个类。在此之前,我们回忆一下Java中的四种引用类型,相关GC只是参考前面系列的文章(JVM相关)
①强引用:Java中默认的引用类型,一个对象如果具有强引用那么只要这种引用还存在就不会被GC。
②软引用:简言之,如果一个对象具有弱引用,在JVM发生OOM之前(即内存充足够使用),是不会GC这个对象的;只有到JVM内存不足的时候才会GC掉这个对象。软引用和一个引用队列联合使用,如果软引用所引用的对象被回收之后,该引用就会加入到与之关联的引用队列中
③弱引用(这里讨论ThreadLocalMap中的Entry类的重点):如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器GC掉(被弱引用所引用的对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。弱引用也是和一个引用队列联合使用,如果弱引用的对象被垃圾回收期回收掉,JVM会将这个引用加入到与之关联的引用队列中。若引用的对象可以通过弱引用的get方法得到,当引用的对象呗回收掉之后,再调用get方法就会返回null
④虚引用:虚引用是所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。(不能通过get方法获得其指向的对象)
总结:THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。
翻外:ThreadLocalMap 中会有一个 Entry 类型的数组,名字叫 table。我们可以把 Entry 理解为一个 map,其键值对为
键,当前的 ThreadLocal
值,实际需要存储的变量,比如 user 用户对象或者 simpleDateFormat 对象等
ThreadLocalMap 既然类似于 Map,所以就和 HashMap 一样,也会有包括 set、get、rehash、resize 等一系列标准操作。但是,虽然思路和 HashMap 是类似的,但是具体实现会有一些不同
比如其中一个不同点就是,我们知道 HashMap 在面对 hash 冲突的时候,采用的是拉链法。它会先把对象 hash 到一个对应的格子中,如果有冲突就用链表的形式往下链,但是 ThreadLocalMap 解决 hash 冲突的方式是不一样的,它采用的是线性探测法。如果发生冲突,并不会用链表的形式往下链,而是会继续寻找下一个空的格子。这是 ThreadLocalMap 和 HashMap 在处理冲突时不一样的点。
参考:
https://www.cnblogs.com/fsmly/p/11020641.html
https://blog.csdn.net/vincent_wen0766/article/details/108598994