ThreadLocal与ThreadLocalMap源码分析 (5)

case3:增加元素后可能超过阈值导致的扩容处理

private void rehash() { // 清除所有过时节点 expungeStaleEntries(); // 在清除所有过时节点后,如果数量超过3/4的阈值,则进行扩容处理 // setThreshold()方法非公有,threshold值一直为数组长度的2/3,所以这里是超过数组长度一半就进行扩容 if (size >= threshold - threshold / 4) resize(); } /** * 双倍扩容 */ private void resize() { // 获取旧数组和长度 Entry[] oldTab = table; int oldLen = oldTab.length; // 新数组长度为原来的两倍 int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; // 遍历原数组元素 for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; // 如果为非null节点 if (e != null) { ThreadLocal<?> k = e.get(); // 如果是过时节点,则将value置为null,可以使得value的实体尽快被回收 if (k == null) { e.value = null; // Help the GC } else { // 如果是正常节点,计算下标,重新填入新数组(开放地址解决hash冲突) int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; // 新数组元素个数+1 count++; } } } // 重新设置阈值 setThreshold(newLen); size = count; // 将变量table指向新数组 table = newTab; } ThreadLocalMap-内存泄露问题以及对设计的一些思考

先来聊一聊内存泄漏这个概念。我的理解是有一块内存空间,如果不再被使用但又不能被垃圾回收器回收掉,那么就相当于这块内存少了这块空间,即出现了内存泄露问题。如果内存泄露的空间一直在积累,那么最终会导致可用空间一直减少,最终可能导致程序无法运行。

ThreadLocalMap中也是有可能会出现该问题的,map中entry节点的key为弱引用,如果key没有其它强引用,是会被垃圾收集器回收的。回收之后,map中该节点的value就不会再被使用,但value又被entry节点强引用,不会被回收。这就相当于value这块内存空间发生了泄露。所以能看到在源码中很多方法都进行了清除过时节点的操作,为的就是尽量避免内存泄漏。

在看源码时,一直在思考为什么entry节点的键要采用弱引用的方式。不妨反过来思考,如果entry节点将threadLocal对象作为一个成员变量,而不是采用弱引用的方式,那么entry节点一直对key和value保持着强引用关系,即使threadlocal对象在其它地方都不再使用,该对象也不会被回收。这就会导致entry节点永远不会被回收(只要线程不终结),而且也不能主动去判断是否切断map中threadlocal对象的引用(不知道是否还有其它地方引用到了)。

因为map是Thread对象的一个成员变量,线程不终结,map是不会被回收的,如果发生了内存泄露的问题,可能会一直积累下去,最终导致程序发生异常。而key采用弱引用加之主动的判断过时节点(判断是否过时很简单,看key是否为null即可)并进行清除处理可以最大限度的减少内存泄露的发生。

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

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