一、ThreadLocal的使用
ThreadLoca是一个本地的线程变量,他能够做到线程与线程之间的隔离,它其中填充的变量只属于本线程,它为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
他的用法也非常简单:
ThreadLocal<String> threadLocal=new ThreadLocal<>();
threadLocal.set("1");
threadLocal.get();
threadLocal.remove();
以上的api就是ThreadLocal的主要api,主要就是这三个,一个是设置线程变量,一个是获取,最后是清空。ThreadLocal的泛型的类型就是所要存储对象的类型。set(“1”)执行以后就相当于在本线程中存在了一个存储着“1”这个字符串的一个变量,该变量只对本线程可见。
二、ThreadLocal源码分析
1.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);
}
在set方法中首先获取到了当前的线程t,然后通过getMap方法将t传入获得了一个ThreadLocalMap。来看下getMap的代码:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
最终返回了一个t中的变量threadLocals也就是说这个变量是属于线程Thread类的,因为t是当前线程对象。然后看下Thread中的这个变量的定义:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
默认是个null值,回到set方法,做了个map!=null的判断,因为初始化的时候这个map是个null所以会走else分支,执行 createMap(t, value)方法。在这个方法中最终创建了一个ThreadLocalMap并且赋值给threadLocals。在创建的时候传入了一个this作为key然后传入的值作为value,此时的this就是当前threadLocal对象。最终也就是创建了一个map并且map中的key为当前threadLocal对象,value为传入的数据。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
然后看下ThreadLocalMap的构造方法,
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
会发现在其中创建了一个Entry数组,数组的大小为初始化容量16,如果点击进去会发现Entry类继承了一个虚引用。然后将key进行hash计算。最终执行了一个setThreshold方法扩大了threshold阈值。也就是将来这个参数作为扩容的标志,如果map的长度大于了总长度的三分之二就要进行扩容。至此形成了一个图:
图中左侧是虚拟机栈右侧是堆,左侧存放的是对象的引用,右侧是实际的对象开辟的内存空间,当创建了一个ThreadLocal对象以后,栈中的引用是强指向的关系,同时通过上边的分析得知,这个ThreadLocal对象也作为了ThreadLocalMap的key,然后ThreadLoacalMap的主要数据结构就是一个Entry数组,所以也就是Entry中的某个entry中的key指向了创建的ThreadLocal对象,由于entry继承了WeakReference类,同时将其key的指向改变成弱引用,也就是最终形成了key到ThreadLocal对象的引用变成了若引用指向。
在栈中还有一个当前线程对象的引用,它指向了堆中的线程对象,而真正的ThreadLocalMap正是存储在每个线程的threadLocals变量中,也就是threadLocals指向了这个map,也就印证了为什么实现了线程之间的隔离,就是因为这个map是放在每个线程之中的。
这样设置成虚引用的好处是什么?这是因为当方法执行结束以后,栈中的数据会随之弹栈,然后这时候栈中的引用也就不存在了,如果key到ThreadLocal对象是一个强引用那么此时ThreadLocal对象则不能被回收,但是此时如果线程还没有销毁,也就是线程的引用还存在着,那么ThreadLocalMap的也不会被回收,但是显然这个map不会被使用了,因为栈中的ThreadLocal的引用已消失,也就是获取不到key值了,所以随着这样的情况反复出现就会造成内存泄漏,而如果设置成弱引用就会尽量避免key值得内存泄漏,因为key是若引用指向了ThreadLocal对象所以在下次GC发生时就会回收掉ThreadLocal对象。但是这样也避免不了value得内存泄漏的发生,所以在阿里规约中约定必须要使用ThreadLocal中得remove方法进行map的移除操作。