简介
ThreadLocal
外部定义了一个动态外部类 ThreadLocalMap
,这个 Map 的 key 就是以后的ThreadLocal
,value 则是要存储的线程局部变量。精确的说 key 保留的是ThreadLocal
的弱援用。在每个线程外部会保护这样的 ThreadLocal.ThreadLocalMap
,这样做的益处显而易见,能够做到线程之间隔离,并且随着线程敞开,ThreadLocalMap
以及其中的对象值会被回收。
插入数据
获取以后线程的ThreadLocalMap
,如果已初始化就直接插入,如果未初始化就先初始化再插入。
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
的set
办法
private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;
int len = tab.length;
// 依据 Hash 值计算数据该插入的地位
// 这个 Hash 值得获取是通过一个魔数递增的形式失去
int i = key.threadLocalHashCode & (len-1);
// 遍历桶,直到遇到桶中空槽地位进行
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 为 null, 阐明曾经生效,作为弱援用,已被 GC
// 此时须要清理过期 Key 对应的 value
if (k == null) {replaceStaleEntry(key, value, i);
return;
}
}
// 走到这里,阐明遇到了一个空的槽,则直接插入
tab[i] = new Entry(key, value);
int sz = ++size;
// 先清理过期的槽,并判断是否要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 进行扩容,2 倍,扩容前也会清理过期的槽
rehash();}
能够看出在一个简略的 set
办法中有屡次波及到清理过期的槽,尽管清理的办法有多个,然而最终都是调用 expungeStaleEntry
办法。入参就是某个过期槽在桶中的地位。
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;
int len = tab.length;
// 以后过期的槽置为 null,不便 GC
tab[staleSlot].value = 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();
// 这里将过期的槽置为 null
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 留神:走到这个分支,以后的槽未生效,后面过期槽的地位曾经被腾出
// 获取槽 i 处结点本来应寄存的地位
// 因为 ThreadLocal 解决 Hash 抵触的形式,h 必然小于等于 i
int h = k.threadLocalHashCode & (len - 1);
//h!=i,阐明 i 处寄存的结点不是它本来地位,须要移动
if (h != i) {
// 将该处的结点挪走,置为 null
tab[i] = null;
// 从它本来应寄存的地位 h,开始遍历,// 找到一个最靠近 h 地位的空槽,并插入数据
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
// 返回的桶中空槽的地位
return i;
}
通过下面源码剖析,expungeStaleEntry
办法做了两件事:
- 遍历桶,清理过期的 key 对应的 value 和槽(遇到空槽进行)
- 遇到未生效的槽,尝试将该槽的结点往凑近它本来地位的左近移动
对于第二点,咱们须要补充一下,ThreadLocal
解决 Hash 抵触的形式:
在往ThreadLocalMap
中 set 值时,如果以后地位曾经被占,且 key 不雷同,那它就往后找一个空的槽寄存,也就是说,Map 桶中元素的地位可能不是该元素自身该当寄存的地位,而是靠后,所以清理过期槽后,尽量把它挪到凑近本来地位左近,这个是为了查问不便
获取值
其实剖析完了 set 办法,咱们大抵也能猜出查询方法是怎么回事,无非就是依据 key 计数出桶的地位,如果该地位的 key 与以后的 key 不是同一个,那就往后遍历,找到 key 值雷同的结点,遍历过程中顺带手清理一下过期的槽。
public T get() {Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 外围就是这里
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 往 map 中存一个 null 的 Value,并返回
return setInitialValue();}
简略看下 map 中的 get 办法
private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 查到就返回
if (e != null && e.get() == key)
return e;
else
// 查不到就遍历往后找
return getEntryAfterMiss(key, i, e);
}