Java并发编程之ThreadLocal源码分析(3)

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; }

  在看cleanSomeSlots()方法前,还得看expungeStaleEntry()。有时候不得不说,好的java命名规范,真的是很重要啊!看到这个方法的名字就知道它大概是用来清理过期节点的。回想一下,有什么节点是需要我们的清理的吗?好像有。。前面是不是有找到过key为null的entry节点啊?这个key为null的节点好像没啥用啊!

cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);

  还记得这个slotToExpunge吗?不记得了的话往前翻一翻。这个slotToExpunge位置指向了一个key为null的entry节点。既然知道这个节点是没用的,那它就应该被回收。这里就很直接粗暴了,直接把它直接null以待后面垃圾回收器清理。清理完之后,又是一个for循环。如果key为null,将该entry置为null。如果不为null,重新计算一下hash值,如果位置与当前位置不同,需要重新找一个位置放该节点。当然也是利用线性探测法了,找到连续位置后面第一个为null的节点放置。
  最后返回的节点为从slotToExpunge往后的第一个值为null的entry节点。
  再看看cleanSomeSlots方法主体。

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; }

  这个方法顾名思义是用来清理某些节点的。清理啥节点呢?还是那些不为null但是key为null的节点。参数n决定了for循环要执行的次数。>>>在java中是无符号位移的意思,也就是说如果每次循环tab[i]均不需要清理,最多会执行logn次。如果有需要清理的节点,就会调用expungeStaleEntry()方法去回收这个节点。
  上面说了这么一大堆,终于把ThreadLocalMap的set()方法说完了。下面接着来看getEntry()方法。

3.2 ThreadLocalMap.getEntry()方法 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); }

  getEntry()方法比较简单。先根据key值计算出对应在table中的位置,如果table[i]的key值和我们的参数key相同,直接返回table[i];反之,调用getEntryAfterMiss()方法。

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }

  进入到这个方法的情况可能如下三种:

entry节点为null
如果entry节点为null,直接返回null。

entry节点不为null,但是key为null
执行expungeStaleEntry()方法回收该节点,回收完之后,tab[i]节点也就变成了null,直接返回null。

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

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