参考网址: https://www.jianshu.com/p/98b68c97df9b (此处链接不知为何,点击后跳转页面还是本页,建议复制打开)
1、ThreadLocal是什么?
线程本地变量,其为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立的改变自己的副本,而不影响其它线程维护的副本。
在每个线程内部都有自己的一个Map,该Map是ThreadLocal中的一个内部类ThreadLocalMap。在线程对其进行读写时,是从本身拥有的成员变量threadlocals(类型为内部类ThreadLocalMap)中进行读写,该变量是用来存储实际变量副本的。存储时键是当前ThreadLocal变量,值是要保存的变量复本。
扩展:Thread中包含两个ThreadLocal中的ThreadLocalMap成员变量,threadLocals、inheritableThreadLocals
2、ThreadLocal是如何为每个线程创建变量的副本的?
在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,键是当前ThreadLocal变量,值是变量的副本;
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()或set()时,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键,以ThreadLocal要保存的副本变量为value;
3、ThreadLocalMap的解释
是ThreadLocal的内部类,类似于HashMap,但并没有实现Map接口
初始容量是16的Entry<K,V>数组,这里指定K为ThreadLocal对象,并且注意Entry<K,V>是继承自WeakReference(弱引用,生命周期只能存活到下次GC前)
与HashMap最大的不同是,Entry<K,V>没有next,就是说,解决Hash冲突的方式并不是链表的方式,而是采用线性探测法,简单的步长加一或减一(寻找相邻的位置)。该线性探测法解决hash冲突的效率低。
线性探测法:根据初始key的hashcode值确定元素的位置,若该位置已存在一个元素,则根据一定的算法机制去下一处位置,直到存放元素成功。
4、ThreadLocal的get()和set()方法解释
get():
获取当前线程的ThreadLocalMap对象ThreadLocals;
从map中获取线程存储的Entry<K,V>节点;
从Entry<K,V>节点获取存储的Value副本值返回;
map为空的话返回初始值null,即线程变量副本为null,在使用时需要注意判断NullPointerException。
set():
获取当前线程的成员变量map;
map非空,则重新将ThreadLocal和新的value副本放入到map中;
map空,则对线程的成员变量ThreadLocalMap进行初始化创建,并将ThreadLocal和value副本放入map中。
5、ThreadLocal的问题,会造成内存泄露
ThreadLocalMap中的键是弱引用的,而value是强引用的,这样会导致ThreadLocal在没有外部对象强引用时,发生GC时弱引用key会被回收,而value不会被回收。此时创建ThreadLocal的线程还在运行,时间过长后,加之Entry对象的value一直得不到回收,发生内存泄露。
为此,在调用ThreadLocal的get()、set()后,需要调用remove()方法,将Entry节点和Map的引用关系溢出,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC时就可以完成正确的回收。
6、ThreadLocal的应用场景
只适用于独立副本的情况,例如数据库连接、Session管理等