这里将移除元素的方法放在前面,是因为其它部分会频繁使用过时节点的移除方法。先理解这部分内容有助于后续理解其他部分。
根据key移除容器元素的方法:
private void remove(ThreadLocal<?> key) {
// 计算索引下标
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 从下标i处开始向后寻找是否有key对应节点,直到遇到Null节点
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 如果遇到key对应节点,执行移除操作
if (e.get() == key) {
// 移除节点的键(弱引用)
e.clear();
// 移除该过时节点
expungeStaleEntry(i);
return;
}
}
}
移除过时节点的执行方法:
移除过时节点除了将该节点置为null之外,还要对该节点之后的节点进行移动,看看能不能往前找合适的空格转移。
这种方法有点类似jvm垃圾回收算法的标记-整理方法。都是将垃圾清除之后,将剩余元素进行整理,变得更紧凑。这里的整理是需要强制执行的,目的是为了保证开放地址法一定能在连续的非null节点块中找到已有节点。(试想,如果把过时节点移除而不整理,该节点为null,将前后节点分开了。而如果后面有某个节点hash计算的下标在前面的节点块,在查找节点时通过开放地址会找不到该节点)。示意图如下:
private int expungeStaleEntry(int staleSlot) {
// 获取entyy数组和长度
Entry[] tab = table;
int len = tab.length;
// 清除staltSlot节点的值的引用,清除节点的引用
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 容器元素个数-1
size--;
// 清除staleSlot节点后的整理工作
// 将staleSlot索引后的节点计算下标往前插空移动
Entry e;
int i;
// 遍历连续的非null节点,直到遇到null节点
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// case1:如果遍历到的节点是过时节点,将该节点清除,容器元素数量-1
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// case2:如果遍历到的节点不是过时节点,重新计算下标
int h = k.threadLocalHashCode & (len - 1);
// 当下标不是当前位置时,从hash值计算的下标h处,开放地址往后顺延插空
if (h != i) {
// 先将该节点置为null
tab[i] = null;
// 找到为null的节点,插入节点
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
移除所有过时节点的方法:很简单,全局遍历,移除所有过时节点。
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
// 遇到过时节点,清除整理该节点所在的连续块
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
尝试去扫描一些过时节点并清除节点,如果有节点被清除会返回true。这里只执行了logn次扫描判断,是为了在不扫描和全局扫描之间找到一种平衡,是上面的方法的一个平衡。
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
// 遇到过时节点,清除整理该节点所在连续块
if (e != null && e.get() == null) {
n = len;
removed = true;
// 从该连续块后第一个null节点开始
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
ThreadLocalMap-获取元素