乐于分享
好东西不私藏

ThreadLocal源码解析—2

ThreadLocal源码解析—2

上一章介绍了在ThreadLocal的set()方法中,如果遇到了过期槽位,会触发ThreadLocal自动扫描清理Entry的机制。主要是下面这行代码:
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
包含了两个方法,expungeStaleEntry与cleanSomeSlots。下面将逐个介绍两个清理方法,首先看expungeStaleEntry
privateintexpungeStaleEntry(int staleSlot) {      Entry[] tab = table;      int len = tab.length;      // expunge entry at staleSlot      tab[staleSlot].value = null;      tab[staleSlot] = null;      size--;      // Rehash until we encounter null      Entry e;      int i;      for (i = nextIndex(staleSlot, len);         (e = tab[i]) != null;         i = nextIndex(i, len)) {                 ThreadLocal<?> k = e.get();                 if (k == null) {                         e.value = null;                          tab[i] = null;                          size--;                  } else {                          int h = k.threadLocalHashCode & (len - 1);              if (h != i) {                                   tab[i] = null;                                   // Unlike Knuth 6.4 Algorithm R, we must scan until                                    // null because multiple entries could have been stale.                                    while (tab[h] != null)                                            h = nextIndex(h, len);                                    tab[h] = e;                           }                   }       }       return i;}
1.方法的入参是一个过期槽位的index,进入方法后,首先将该槽位的Entry的value引用置为空,防止了该槽位内存泄漏,同时将Entry的size减1。
2.然后从该过期槽位开始向后逐一扫描,直到遇到空槽位停止。扫描时,当遇到过期槽位,同样将该槽位的Entry的value引用置为空,同时将Entry的size减1,防止内存泄漏。
3.如果遇到的不是过期槽位,而是有效槽位,则根据该槽位的ThreadLocal持有的threadLocalHashCode计算该ThreadLocal本应处于的位置h,与该ThreadLocal实际所处的位置i相比较,如果相等,则说明无冲突,跳过。
4.如果h和i不相等,则说明该ThreadLocal set时存在冲突,触发rehash。即将该i位置Entry置为空。尝试将原i位置的Entry放入本应放入的h位置上,如果h位置不为空,则说明有冲突,则拉链法继续向后寻找h的位置直到h位置为空,能够放入Entry对象为止。
5.最后将遇到的第一个空槽位的位置返回。
可以看到这个方法相当于在传入的 【失效位置——>首个空槽位】之间进行了一次逐个的过期槽位回收与rehash,一定程度上防止了内存泄漏。
再看第二个方法
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;                        i = expungeStaleEntry(i);                 }        } while ( (n >>>= 1) != 0);        return removed;}
1.该方法的入参包含两个,i和n,i是上一个清理方法返回的结果,代表的是一个空槽位的位置。而n传入的则是Entry数组的长度。
2.这个方法比较简单,逻辑主要集中在do while循环里。while循环的终止条件为n != 0,每次循环n无符号右移1位,相当与除2,如果n为16,则循环4次.需要注意的是当拉链法检测到存在过期Entry时,会重置n的次数。所以当一个过期Entry都没有时,循环4次停止,若检测到一个过期key,循环次数会增加。
3.当检测到过期Entry时,并不自己处理,而是将其将给expungeStaleEntry方法处理。相当于又做了一次 【失效位置——>首个空槽位】之间进行了一次逐个的过期槽位回收与rehash。
以上为ThreadLocal里的过期key的检测回收的两个方法,下一节将会介绍ThreadLocal的另外两个方法,get()与remove()。