threadLocal源码分析
- threadLocal.set()方法
- threadLocalMap构造
- 如何把threadLocal保存到map
- threadLocalMap如何解决hash冲突
- threadLocalMap如何重新计算元素的位置
threadLocal.set()方法
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
通过此方法我们可以看到ThreadLocalMap是被每个线程独立拥有的,每个线程持有一个map的引用,这也是ThreadLocal实现线程隔离的思想
这样就把要保存数据的生命周期交给了thread自己管理,thread销毁的同时,保存的数据也一起跟着被销毁
threadLocalMap构造
class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold;
}
可以看出这并不是我们平常所看到的map,是通过table数组保存entry对象,同时每个entry对象保存相应的key-value
如何把threadLocal保存到map
思考以下问题:
- 怎么计算数组下标?
- 两个元素插入时候数组下标相同时,如何处理这种冲突?
带着问题的开始寻找答案
threadLocalMap如何解决hash冲突
这里使用了线性探测法解决hash冲突
看threadLocalMap的set方法:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 通过位运算计算数组下标,位运算保证i的值小于等于len-1
int i = key.threadLocalHashCode & (len-1);
/**
* 插入的时候发生了hash冲突
*/
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;
}
/**
* 数组元素不等于null,但是key等于null,说明ThreadLocal弱引用已经被回收
* 但是value依然存在,需要释放该元素同时插入新的元素
*/
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
// 如果上面两个条件都不满足
// 继续寻找下一个null的槽位,i的值一直在变化
}
/**
* 没有产生hash冲突的话,就直接在该数组位置插入
*/
tab[i] = new Entry(key, value);
int sz = ++size;
// 双重判断
// 1 首先判断是否有可以被回收的节点
// 2 判断size是否超过了临界值
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
threadLocalMap如何重新计算元素的位置
我们先看map删除一个元素都做了什么操作,然后一步步分析
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//1 expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//2 开始整理工作
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
/** 3
* 开始清理工作,说明这个threadlocal已经被gc回收了
* 但是value作为强引用依然没有被回收,这里需要进行一个清理工作
*/
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
//4 把e放到它正真应该存在的位置去
if (h != i) {
// 释放当前槽位
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
// 发现本属于自己位置还是依然被占用着
// 继续寻找插入e的位置(用开放寻址法来解决散列冲突)
// 5
while (tab[h] != null)
h = nextIndex(h, len);
// 6
tab[h] = e;
}
}
}
return i;
}
- 把相应的元素位置置为null,删除该元素
- 遍历所有不为null的元素
- 如果发现元素的key==null说明threadLocal引用已经被回收,需要把该元素置为null,等待下次垃圾回收
- 如果key不等于null,查看元素所处的位置和计算的hash是否是一致的,如果不一致,重新查找它的正确位置
- 如果正确的位置已经被占用了,那就继续查找空闲的位置
- 找到空闲位置之后赋值