adLocalMap map = getMap(t);
// 如果ThreadLocal已经设过值,直接设值,否则初始化
if (map != null)
// 设值的key就是当前ThreadLocal对象实例,value是ThreadLocal泛型对象值
map.set(this, value);
else
// 初始化ThreadLocalMap
createMap(t, value);
}
再看一下实际的set方法源码:
// key就是当前ThreadLocal对象实例,value是ThreadLocal泛型对象值
private void set(ThreadLocal<?> key, Object value) {
// 获取ThreadLocalMap中的Entry数组
Entry[] tab = table;
int len = tab.length;
// 计算key在数组中的下标,也就是ThreadLocal的hashCode和数组大小-1取余
int i = key.threadLocalHashCode & (len - 1);
// 查找流程:从下标i开始,判断下标位置是否有值,
// 如果有值判断是否等于当前ThreadLocal对象实例,等于就覆盖,否则继续向后遍历数组,直到找到空位置
for (Entry e = tab[i];
e != null;
// nextIndex 就是让在不超过数组长度的基础上,把数组的索引位置 + 1
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 如果等于当前ThreadLocal对象实例,直接覆盖
if (k == key) {
e.value = value;
return;
}
// 当前key是null,说明ThreadLocal对象实例已经被GC回收了,直接覆盖
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 找到空位置,创建Entry对象
tab[i] = new Entry(key, value);
int sz = ++size;
// 当数组大小大于等于扩容阈值(数组大小的三分之二)时,进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set方法具体流程如下:
从源码和流程图中得知,ThreadLocal是通过线性探测法解决哈希冲突的,线性探测法具体赋值流程如下:
- 通过key的hashcode找到数组下标
- 如果数组下标位置是空或者等于当前ThreadLocal对象,直接覆盖值结束
- 如果不是空,就继续向下遍历,遍历到数组结尾后,再从头开始遍历,直到找到数组为空的位置,在此位置赋值结束
线性探测法这种特殊的赋值流程,导致取值的时候,也要走一遍类似的流程。
4.3 get方法源码
// 从ThreadLocal从取值
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 通过ThreadLocal实例对象作为key,在Entry数组中查找数据
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果不为空,表示找到了,直接返回
if (e != null) {
T result = (T)e.value;
return result;
}
}
// 如果ThreadLocalMap是null,就执行初始化ThreadLocalMap操作
return setInitialValue();
}
再看一下具体的遍历Entry数组的逻辑:
// 具体的遍历Entry数组的方法
private Entry getEntry(ThreadLocal<?> key) {
// 通过hashcode计算数组下标位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 如果下标位置对象不为空,并且等于当前ThreadLocal实例对象,直接返回
if (e != null && e.get() == key)
return e;
else
// 如果不是,需要继续向下遍历Entry数组
return getEntryAfterMiss(key, i, e);
}
再看一下线性探测法特殊的取值方法:
// 如果不是,需要继续向下遍历Entry数组
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 循环遍历数组,直到找到ThreadLocal对象,或者遍历到数组为空的位置
while (e != null) {
ThreadLocal<?> k = e.get();
// 如果等于当前ThreadLocal实例对象,表示找到了,直接返回
if (k == key)
return e;
// key是null,表示ThreadLocal实例对象已经被GC回收,就帮忙清除value
if (k == null)
expungeStaleEntry(i);
else
// 索引位置+1,表示继续向下遍历
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
// 索引位置+1,表示继续向下遍历,遍历到数组结尾,再从头开始遍历
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
ThreadLocal的get方法流程如下:
4.4 remove方法源码
remove方法流程跟set、get方法类似,都是遍历数组,找到ThreadLocal实例对象后,删除key、value,再删除Entry对象结束。
public void remove() {
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
// 具体的删除方法
private void remove(ThreadLocal<?> key) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
// 计算数组下标
int i = key.threadLocalHashCode & (len - 1);
// 遍历数组,直到找到空位置,
// 或者值等于当前ThreadLocal对象,才结束
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 找到