参考资料:https://www.cnblogs.com/micrari/p/6790229.html
参考资料:https://blog.csdn.net/anlian523/article/details/105523826
它不是用来解决多线程环境下的共享变量问题,而是用来提供线程内部的共享变量,在多线程环境下,可以保证各个线程之间的变量互相隔离、相互独立。
ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。
底层原理:每一个线程其内部维护了一个ThreadLocalMap映射表,key存储当前的ThreadLocal,value是存储我们的业务对象,仅仅是一个工具。
为什么ThreadLocal可以实现线程隔离的线程私有变量:因为它把这个Map对象直接作为了Thread对象的成员,这样,每个运行的线程都对应到一个唯一的Thread对象,而每个Thread对象都保存着各自的ThreadLocal.ThreadLocalMap类型的成员变量。
为什么要弱引用:因为如果这里使用普通的key-value形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。
创建ThreadLocal对象的两种方式:
直接new:ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {protected Integer initialValue() {return 1;}};
调静态方法:ThreadLocal<Integer> integerThreadLocal = ThreadLocal.withInitial(() -> 1);
public T get() { //获取与当前线程关联的ThreadLocal值
ThreadLocalMap map = Thread.currentThread().threadLocals; //每个thread底层都维护了一个ThreadLocalMap对象
if (map != null) { //已经被初始化过了
//entry继承WeakReference,下一次gc有可能被回收
//key并不是ThreadLocal本身,而是它的一个弱引用
//Entry不是弱可达的,不会被JVM清理。Entry是一个WeakReference对象,弱可达的是放在里面的ThreadLocal对象。
ThreadLocalMap.Entry e = map.getEntry(this); //this为当前的ThreadLocal对象
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
//此i是初始下标,因为存在hash冲突,后续需要采用线性探测法去找
int i = key.threadLocalHashCode & (table.length - 1); //计算key在table上的下标
Entry e = table[i];
if (e != null && e.get() == key)// 对应的entry存在且未失效且弱引用指向的ThreadLocal就是key,则命中返回
return e;
//e可能为null:
//e不为null,但是key没对应上
// 因为用的是线性探测,所以往后找还是有可能能够找到目标Entry的。
return getEntryAfterMiss(key, i, e);
}
/*调用getEntry未直接命中的时候调用此方法 */
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 基于线性探测法不断向后探测直到遇到空entry。不断遍历table中的entry
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key) return e;
if (k == null) //通过弱引用的key被回收了
expungeStaleEntry(i); //删除过期的entry,value不会被回收同时又不能被访问到,导致内存泄漏了
else
i = nextIndex(i, len); //模拟环形:下一个,如果到了最后一个,就从第一个开始
e = tab[i];
}
//如果考虑了哈希冲突后,开放寻址还是不能找到entry,那么说明该key确实不在map中
return null;
}
private int expungeStaleEntry(int staleSlot) { //删除过期的entry,key被gc回收了,导致在垃圾回收的时候进行可达性分析的时候,value可达从而不会被回收掉,但是该value永远不能被访问到,这样就存在了内存泄漏
Entry[] tab = table;
int len = tab.length;
// 因为entry对应的ThreadLocal已经被回收,value设为null,显式断开强引用
tab[staleSlot].value = null;
// 显式设置该entry为null,以便垃圾回收
tab[staleSlot] = null;
size--;
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
public void remove() { //将与当前线程关联的ThreadLocal值删除
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private T setInitialValue() {
T value = initialValue(); //初始值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); //更新value
else
t.threadLocals = new ThreadLocalMap(this, value); //初始化threadLocals
return value;
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[16]; //初始化容量=16
int i = firstKey.threadLocalHashCode & 15; //计算索引位置
table[i] = new Entry(firstKey, firstValue);//entry extends WeakReference 如果没有任何引用指向,下一次gc被回收
size = 1;
setThreshold(INITIAL_CAPACITY);
}