3.7> cleanSomeSlots(int i, int n)
返回true:表示存在“陈旧”的Entry且已经被清除(但并不表示完全清除所有的“陈旧”Entry,只表示执行过这种操作)
- 由于上面的expungeStaleEntry方法,已经在“施工”范围内,清除了所有“陈旧的”Entry,并且由于在这个范围内,是不包含空位置的,所以可以顺利的把这个范围内的所有“陈旧”Entry清除掉。
- 那么cleanSomeSlots方法,则是以log2(n)的粒度,去清除一些“陈旧”Entry。
- 方法上的注释翻译如下,可以理解为是对于提升插入速度和table数组内“陈旧”Entry整理耗时的一种平衡处理方案:
启发式扫描一些单元格以查找陈旧条目。当添加新元素或删除另一个陈旧元素时调用此方法。它执行对数扫描,作为不扫描(快速但保留垃圾)和扫描次数与元素数量成正比之间的平衡,这将找到所有垃圾但会导致某些插入花费 O(n) 时间。
【解释】
removed如果为false,则可以理解为table数组里基本没有“陈旧”Entry。rehash是否执行的判断依据,其实用到了removed这个结果。
这就表示table数组中基本都是正常的Entry,并且触达到了阈值长度,那么就可以执行rehash操作了。从而避免了table数组由于存在大量“陈旧”Entry而导致rehash的情况发生。
3.8> rehash()
1> 遍历table数组,清除表中的所有“陈旧”Entry。
2> 如果满足数组中存在的Entry数量 >= 3/4threshold,则进行resize()扩容操作。
3.9> expungeStaleEntries()
- 该方法内部比较简单,就是遍历table数组里的Entry,调用expungeStaleEntry方法(expungeStaleEntry详情上面介绍了,这里就不再赘述了)
- 源码和注释如下所示:
3.10> resize()
- 扩容操作执行如下操作:
- 按照原table数组长度,创造长度为2倍的新table数组。
- 将旧table数组中的Entry插入到全新的table数组中,具体插入方式采用“开发地址法”。(前面也说过了,这里也不赘述了)
- 根据新的table数组,更新全局变量:table、size、threshold。
- 源码和注释如下所示:
四、ThreadLocal 内存溢出问题:
- 通过上面的分析,我们知道
expungeStaleEntry()
方法是帮助垃圾回收的,根据源码,我们可以发现 get 和set 方法都可能触发清理方法expungeStaleEntry()
,所以正常情况下是不会有内存溢出的。 - 但是如果我们没有调用get和set的时候就会可能面临着内存溢出。
- 养成好习惯不再使用的时候调用remove(),加快垃圾回收,避免内存溢出。
- 退一步说,就算我们没有调用get和set和remove方法,线程结束的时候,也就没有强引用再指向ThreadLocal中的ThreadLocalMap了,这样ThreadLocalMap和里面的元素也会被回收掉。
- 但是有一种危险是,如果线程是线程池的,在线程执行完代码的时候并没有结束,只是归还给线程池,这个时候ThreadLocalMap和里面的元素是不会回收掉的。
(完)