ThreadLocal与ThreadLocalMap源码分析 (3)

这里将移除元素的方法放在前面,是因为其它部分会频繁使用过时节点的移除方法。先理解这部分内容有助于后续理解其他部分。

根据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计算的下标在前面的节点块,在查找节点时通过开放地址会找不到该节点)。示意图如下:

ThreadLocal与ThreadLocalMap源码分析

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-获取元素

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzpjdg.html