菜鸟笔记
提升您的技术认知

史上最全threadlocal 详解(二)-ag真人游戏

1、threadlocal 使用原理

       前文我们讲过threadlocal的主要用途是实现线程间变量的隔离,表面上他们使用的是同一个threadlocal, 但是实际上使用的值value却是自己独有的一份。用一图直接表示threadlocal 的使用方式。

图1

从图中我们可以当线程使用threadlocal 时,是将threadlocal当做当前线程thread的属性threadlocalmap 中的一个entry的key值,实际上存放的变量是entry的value值,我们实际要使用的值是value值。value值为什么不存在并发问题呢,因为它只有一个线程能访问。threadlocal我们可以当做一个索引看待,可以有多个threadlocal 变量,不同的threadlocal对应于不同的value值,他们之间互不影响。threadlocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

 entry将threadlocal作为key,值作为value保存,它继承自weakreference,注意构造函数里的第一行代码super(k),这意味着threadlocal对象是一个「弱引用」。可以看图1.

static class entry extends weakreference> {
    /** the value associated with this threadlocal. */
    object value;
    entry(threadlocal k, object v) {
        super(k);
        value = v;
    }
}

主要两个原因
1 . 没有手动删除这个 entry
2 . currentthread 当前线程依然运行

        第一点很好理解,只要在使用完下 threadlocal ,调用其 remove 方法删除对应的 entry ,就能避免内存泄漏
        第二点稍微复杂一点,由于threadlocalmap 是 thread 的一个属性,被当前线程所引用,所以threadlocalmap的生命周期跟 thread 一样长。如果threadlocal变量被回收,那么当前线程的threadlocal 变量副本指向的就是key=null, 也即entry(null,value),那这个entry对应的value永远无法访问到。实际私用threadlocal场景都是采用线程池,而线程池中的线程都是复用的,这样就可能导致非常多的entry(null,value)出现,从而导致内存泄露。
综上, threadlocal 内存泄漏的根源是:
    由于threadlocalmap 的生命周期跟 thread 一样长,对于重复利用的线程来说,如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏.

3.1 、key 如果是强引用

     那么为什么threadlocalmap的key要设计成弱引用呢?其实很简单,如果key设计成强引用且没有手动remove(),那么key会和value一样伴随线程的整个生命周期。

   1、假设在业务代码中使用完threadlocal, threadlocal ref被回收了,但是因为threadlocalmap的entry强引用了threadlocal(key就是threadlocal), 造成threadlocal无法被回收。在没有手动删除entry以及currentthread(当前线程)依然运行的前提下, 始终有强引用链currentthread ref → currentthread →map(threadlocalmap)-> entry, entry就不会被回收( entry中包括了threadlocal实例和value), 导致entry内存泄漏也就是说: threadlocalmap中的key使用了强引用, 是无法完全避免内存泄漏的。请结合图1看。

3.3  那么为什么 key 要用弱引用

     事实上,在 threadlocalmap 中的set/getentry 方法中,会对 key 为 null(也即是 threadlocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的.这就意味着使用threadlocal , currentthread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 threadlocal 会被回收.对应value在下一次 threadlocai 调用 get()/set()/remove() 中的任一方法的时候会被清除,从而避免内存泄漏.

 

3.4 如何正确的使用threadlocal

 1、将threadlocal变量定义成private static的,这样的话threadlocal的生命周期就更长,由于一直存在threadlocal的强引用,所以threadlocal也就不会被回收,也就能保证任何时候都能根据threadlocal的弱引用访问到entry的value值,然后remove它,防止内存泄露

 2、每次使用完threadlocal,都调用它的remove()方法,清除数据。
 

网站地图