ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。
ThreadLocal内存结构图
由内存结构图可以看出:
- 每一个Thread对象中都持有一个ThreadLocalMap的成员变量。
- ThreadLocalMap内部维护了一个Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
以上两点在源码中可以更清晰地看出来。
ThreadLocal的重要属性在源码中,ThreadLocal有以下重要属性:
//当前ThreadLocal的HashCode,由下面的nextHashCode()计算得到,用于计算当前ThreadLocal在ThreadLocalMap中的索引位置。
private final int threadLocalHashCode = nextHashCode();
//保证每个ThreadLocal的ThreadLocalHashCode是唯一的
private static AtomicInteger nextHashCode =
new AtomicInteger();
//哈希魔数
private static final int HASH_INCREMENT = 0x61c88647;
//用于返回计算出的下一个哈希值
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
其中的哈希魔数HASH_INCREMENT转化为十进制是 1640531527,2654435769 转换成 int 类型就是 -1640531527,2654435769 等于 (√5-1)/2 乘以 2 的 32 次方。(√5-1)/2 就是黄金分割数,近似为 0.618。
也就是说 0x61c88647 可以理解为一个黄金分割数乘以 2 的 32 次方,它可以保证 nextHashCode 生成的哈希值,均匀的分布在 2 的幂次方上,且小于 2 的 32 次方。
ThreadLocal还有一个重要属性,就是ThreadLocalMap。
ThreadLocalMapThreadLocalMap是在ThreadLocal中的一个静态内部类,部分源码如下:
static class ThreadLocalMap {
static class Entry extends WeakReference> {
//当前线程关联的value
Object value;
//创建键值对,键为ThreadLocal的引用
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
//初始容量为16,必须为2的幂
private static final int INITIAL_CAPACITY = 16;
//键值对的实体数组,长度必须为2的幂
private Entry[] table;
//ThreadLocalMap的长度(元素个数)
private int size = 0;
//扩容的阈值,默认为数组大小的三分之二
private int threshold;
源码中有一个静态内部类Entry。Entry继承自WeakReference即设置为弱引用,如果设置为强引用,即使把ThreadLocal设置为null,GC也不会将其回收。
通过源码可以看出,ThreadLocalMap实际上是一个底层为数组的简单Map结构,每个数据用Entry保存。Entry的键是ThreadLocal的引用,值就是ThreadLocal的值。
ThreadLocal的set()方法源码如下:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//当前线程为Key,找到对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//若map不为null则直接添加到本地变量,Key为当前ThreadLocal,值为添加的值
//若map为null则先创建对应的map
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
先获取出当前的线程t,使用getMap()方法获取当前线程t对应的map。其中getMap()方法源码如下:
ThreadLocalMap getMap(Thread t) {
//返回当前的threadLocals
return t.threadLocals;
}
然后判断getMap()方法调用后的map不为null,就直接将值设置进map中;如果为null,则调用createMap()方法创建一个新map。createMap()方法源码如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal的get()方法
源码如下:
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//当前线程为Key,找到对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果map不为空,就通过map找到对应Entry,再通过Entry的Key找到value并返回
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//若map为空,则初始化
return setInitialValue();
}
先获取出当前的线程t,使用getMap()方法获取当前线程t对应的map。如果map存在,则获取当前ThreadLocal对应的value值;如果map不存在,则调用setInitialValue()方法进行初始化。setInitialValue()源码如下:
private T setInitialValue() {
//initialValue()返回null,即将value置null
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//当前线程为Key,找到对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//若map不为null则直接添加到本地变量,Key为当前ThreadLocal,值为添加的值
//若map为null则先创建对应的map
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
return value;
}
ThreadLocal的remove()方法
源码如下:
public void remove() {
//获取当前线程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
//若map存在,则调用ThreadLocalMap的remove()方法
if (m != null) {
m.remove(this);
}
}
先获取出当前的线程对应的map,然后调用ThreadLocalMap的remove()方法对map中的元素进行删除。
ThreadLocal内存泄漏ThreadLocal在没有外部强引用时,发生GC时会被回收,那么ThreadLocalMap中保存的key值就变成了null,而Entry又被ThreadLocalMap对象引用,ThreadLocalMap 对象又被 Thread 对象所引用,那么当Thread一直不终结的话,value对象就会一直存在于内存中,也就导致了内存泄漏,直至Thread被销毁后,才会被回收。
在使用完ThreadLocal变量后,需要我们手动remove掉,防止ThreadLocalMap中Entry一直保持对value的强引用,导致value不能被回收,进而避免内存泄漏。
总结- 每个线程都有一个属于自己的ThreadLocalMap。
- ThreadLocalMlap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
- 每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
https://baijiahao.baidu.com/s?id=1663127810801876375&wfr=spider&for=pc
https://www.cnblogs.com/fsmly/p/11020641.html



