关于java:重温-JAVA-ThreadLocal-终

5次阅读

共计 3323 个字符,预计需要花费 9 分钟才能阅读完成。

ThreadLocal 是什么

作用

ThreadLocal 用于存储线程间的公有变量

数据结构

内存泄露?

要解释这个问题之前,须要先看 JAVA 对象中的 强援用、软援用、弱援用、虚援用

对象的四种援用类型

  • 强援用
    new 或通过反射创立进去的对象被称为强援用,只有强援用还存在,就不会被垃圾回收
  • 软援用
    应用 SoftReference 润饰的对象被称为软援用,当内存不足时,软援用对象会被回收
  • 弱援用
    应用 WeakReference 润饰的对象被称为弱援用,当对象只有弱援用时,GC 时,该对象会被回收
  • 虚援用
    应用 PhantomReference 润饰的对象被称为虚援用,当对象被回收时会收到零碎告诉

WeakReference 案例介绍

public class WeakReferenceObj extends WeakReference<Object> {public WeakReferenceObj(Object referent) {super(referent);
    }

    public static void main(String[] args) {WeakReferenceObj weak = new WeakReferenceObj(new Object());
        int i = 0;
        while(true){if((weak.get()) != null){
                i++;
                System.out.println("Object is alive for"+i+"loops -"+weak);
            }else{System.out.println("Object has been collected.");
                break;
            }
        }
    }
}

当以上程序运行了一段时间后,WeakReference 指向的对象就会只因被弱援用援用,而将对象回收

若将上诉代码革新为上面的代码

public class WeakReferenceObj extends WeakReference<Object> {public WeakReferenceObj(Object referent) {super(referent);
    }

    public static void main(String[] args) {Object o = new Object();
        WeakReferenceObj weak = new WeakReferenceObj(o);
        int i = 0;
        while(true){System.out.println(o);
            if((weak.get()) != null){
                i++;
                System.out.println("Object is alive for"+i+"loops -"+weak);
            }else{System.out.println("Object has been collected.");
                break;
            }
        }
    }
}

你会发现,不论运行多久,弱援用指向的对象都不会被回收。因为此时的 o 还被一个强援用指向。即 打印流

ThreadLocal 中的内存泄露

ThreadLocal 做为弱援用存在于 ThreadLocalMap key 中。因为是弱援用,当 ThreadLocal 只被弱援用指向时,在触发 GC 后,ThreadLocal 会被回收,即 ThreadLocalMap key 会为 nullvalueThreadLocalMap 强援用指向,导致 value 无奈被回收。ThreadLocalMap 又是 Thread 的一个属性,因而除非 Thread 销毁,ThreadLocalMap 才会被开释,这样一来,Entry 不为 null ,key = null, value 又有值(占着茅坑不拉屎),ThreadLocalMap 如果没有无效的 清理 Entry 不为 null, key = null 的机制,那么就会因为 value 无奈被回收,从而导致内存泄露。

ThreadLocal 清理机制

ThreadLocal 内存泄露的剖析中,咱们晓得,如果 ThreadLocal 没有无效的清理机制,那么必然会导致内存泄露。那么接下来将介绍 ThreadLocal2 种清理机制,避免内存泄露

探测式清理

代码的逻辑在:ThreadLocalMap.expungeStaleEntry
key = null 的地位向前清理,而后遍历 ThreadLocalMap 直到 Entry != null。如果遇到 key = null 则将 Entry、value 设置为 null,如果 key != null, 则从新 hash 从新将该 Entry 放入 ThreadLocalMap 中。

ThreadLocalget(), set(T t),从何处开始清理不大一样,然而最终都是调用 expungeStaleEntry 办法,进行清理。

get()

清理点为:在从 x 下标 获取不到对应 keyvalue 时,会从 x 下标开始清理

set(T t)

清理点为:如果在设置值时,发现在 x 下标 key = null。则会从 x 往前查找 key = null,直到 Entry = null,如果查找到,x 会被赋予方才元素的下标。最初再从 x 处开始清理。

启发式清理

代码的逻辑在:ThreadLocalMap.cleanSomeSlots

i 地位开始,直到以后 ThreadLocalMapEntry 个数 n >>> 1 != 0
如果 Entry != null,但 key = null, 会调用 expungeStaleEntry 进行清理。

如何预防

应用结束后,调用 remove 办法,进行清理。

论断

get、set 办法在外部均会对过期 key 进行清理。然而为了以防万一,在应用结束后,还须要手动调用 remove 办法进行清理

ThreadLocal Hash 算法

ThreadLocal 中有个属性 HASH_INCREMENT = 0x61c88647,它被称为 黄金分割数hash 增量为该数字,因而,产生的 hash 数值十分的平均。

private static final int HASH_INCREMENT = 0x61c88647;

    public static void main(String[] args) {for (int i = 0; i < 16; i++) {
            int hash = HASH_INCREMENT * i + HASH_INCREMENT;
            System.out.print(hash & (16 - 1));
            System.out.print(",");
        }
    }

生成的后果如下:

7,14,5,12,3,10,1,8,15,6,13,4,11,2,9,0,

ThreadLocal Hash 抵触

ThreadLocal Hash 抵触应用的是 线性探测再散列的凋谢寻址法
所谓线性探测算法如下:从以后发生冲突的地位,往后查找,直到找到一个 null 的地位插入。

扩容

当进行 set 后,会执行 cleanSomeSlots 如果有清理元素,且数组大小达到数组扩容阈值 thresholdlen * 2 / 3)则会进行探测式清理。如果清理结束后,数组大小大于 treshold * 3 / 4 则进行扩容。

扩容时,数组变为原来的 2 倍,且将整个 ThreadLocalMapkey 从新 hash 放入 table

灵魂拷问,为什么 ThreadLocalMap key 是弱援用?

key 是强援用

ThreadLocalMap 的生命周期与 Thread 统一。如果 Thread 存活太久,增加了十分多的 ThreadLocal。此时若在代码中将 ThreadLocal 设置为 null,理当被回收。然而,因为 ThreadLocalMap 还存在 ThreadLocal 的强援用,而导致无奈被回收,从而导致内存泄露。并且在代码外面,很难判断 ThreadLocal 在别的中央还有没有援用。

key 是弱援用

ThreadLocal 是弱援用,代码中被设置为 null 后,因为只存在弱援用,所以,在 GC 后会被失常回收。然而 key = null 也会存在 value 内存泄露。尽管 value 会存在内存泄露,然而能够通过判断 key = null 来判断,ThreadLocal 已没有其余援用。

论断

集体认为最外围的起因是:ThreadLocalMap 的生命周期与 Thread 统一。太过难于判断ThreadLocal 只在 ThreadLocalMap 中有援用。因而设置为弱援用,让 GC 回收 ThreadLocal 后,用 null 来判断

参考

面试官:据说你看过 ThreadLocal 源码?我来瞅瞅?

正文完
 0