ThreadLocal实例的弱引用对象会作为key存放在ThreadLocalMap中,然后set方法加入的值就作为ThreadLocalMap中的value。它提供了线程本地变量,可以保证访问到的变量属于当前线程。
属性
private final int threadLocalHashCode = nextHashCode();
//用于计算threadLocal的hash值,每个对象一直递增
private static AtomicInteger nextHashCode =
new AtomicInteger();
// 黄金分割数 使散列更加均匀
private static final int HASH_INCREMENT = 0x61c88647;
//通过cas向上累加
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
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);
}
//获取当前线程中的threadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//初始化一个threadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
通过Thread.currentThread()方法获取了当前的线程引用,通过getMap直接返回Thread实例的成员变量threadLocals。每个线程都有一个ThreadLocal.ThreadLocalMap与ThreadLocal相绑定。
ThreadLocalMap
除了上述属性外,还有一个重要的属性 ThreadLocalMap,ThreadLocalMap 是 ThreadLocal 的静态内部类,当一个线程有多个 ThreadLocal 时,需要一个容器来管理多个 ThreadLocal,ThreadLocalMap 的作用就是管理线程中多个 ThreadLocal,源码如下:
static class ThreadLocalMap {
//键值对的存储结构 继承自弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
// threadLocal为key 被包装成弱引用
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
}
ThreadLocalMap 其实就是一个简单的 Map 结构,底层是数组,有初始化大小,也有扩容阈值大小,数组的元素是 Entry,Entry 的 key 就是 ThreadLocal 的引用,value 是 ThreadLocal 的值。
set
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//计算index
int i = key.threadLocalHashCode & (len-1);
//获取对应index的entry 如果不为空
for (Entry e = tab[i];
e != null;
//采用线性探测解决哈希冲突
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//不为空并且key相等 则替换
if (k == key) {
e.value = value;
return;
}
// key为空说明gc掉了 则替换掉改entry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果为空 则直接新增entry
tab[i] = new Entry(key, value);
int sz = ++size;
//进行启发式的垃圾清理,用于清理无用的Entry
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
expungeStaleEntry
private int expungeStaleEntry(int staleSlot) {
// 1. 将当前的脏entry 置为null,value 置为 null, size,即entry 的数量 减一
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
// 依次循环的使index往后移,直到找到一个 entry = null ,则退出,并返回这个 index
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 在这个过程中,发现脏entry就清除掉,设置为null ,方便GC
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//这里主要的作用是由于采用了开放地址法,所以删除的元素是多个冲突元素中的一个,需要对后面的元素作
//处理,可以简单理解就是让后面的元素往前面移动
//为什么要这样做呢?主要是开放地址寻找元素的时候,遇到null 就停止寻找了,你前面k==null
//的时候已经设置entry为null了,不移动的话,那么后面的元素就永远访问不了了,下面会画图进行解释说明
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
expungeStaleEntry() 方法是帮助垃圾回收的,根据源码,我们可以发现 get 和set 方法都可能触发清理方法expungeStaleEntry(),所以正常情况下是不会有内存溢出的 但是如果我们没有调用get 和set 的时候就会可能面临着内存溢出,养成好习惯不再使用的时候调用remove(),加快垃圾回收,避免内存溢出。
ThreadLocal 内存泄漏
ThreadLocal 在没有外部强引用时,发生 GC 时会被回收,那么 ThreadLocalMap 中保存的 key 值就变成了 null,而 Entry 又被 threadLocalMap 对象引用,threadLocalMap 对象又被 Thread 对象所引用,那么当 Thread 一直不终结的话,value 对象就会一直存在于内存中,也就导致了内存泄漏,所以在使用完 ThreadLocal 变量后,需要我们手动 remove 掉。