ThreadLocal底层原理以及内存泄漏

基本原理

每一个线程Thread中都有一个变量ThreadLocalMap。

在这里插入图片描述

ThreadLocal的全部操作都是基于Thread. ThreadLocalMap进行操作的。

ThreadLocalMap是一个Entry数组,Entry储存Key(WeakReference<ThreadLocal<?>>)和Value(Object)。

在这里插入图片描述

Set过程

在这里插入图片描述

  1. 根据当前线程Thread获取其内部的ThreadLocalMap对象。

  2. 如果是第一次设置值,ThreadLocalMap对象是空值,所以会进行初始化操作,即调用createMap(t,value)方法

  3. 如果不是第一次,就将ThreadLocal对象包装为弱引用对象作为Key,所设的值为value,构造Entry对象,添加到Entry数组中去。
    在这里插入图片描述

  4. set方法中获取key的对应的索引值,并从此位置进行遍历,nextIndex每次将i+1。当有相同的key时,则替换旧值。如果遍历之后无相同key,则创建新值。所以

ThreadLocal处理hash碰撞的方法是使用环形数组,进行线性探测法,依次检测下一个位置。

get过程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 根据当前线程,首先获取ThreadLocalMap对象

  2. 由于ThreadLocalMap使用的当前的ThreadLocal作为key,所以传入的参数为this,然后调用getEntry()方法,通过这个key构造索引,根据索引去table(Entry数组)中去查找线程本地变量,找到Entry对象,然后判断Entry对象e不为空并且e的引用与传入的key一样则直接返回。

  3. 如果找不到则调用getEntryAfterMiss()方法。调用getEntryAfterMiss表示直接散列到的位置没找到,那么顺着hash表递增(循环)地往下找,从i开始,一直往下找,直到找到目标对象或者null。

为什么要使用WeakReference<ThreadLocal<?>>

具有弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

如果不使用弱引用,因为Entry对象包含ThreadLocal对象作为key,所以Entry对象会包含ThreadLocal对象的强引用所以即使在外层将ThreadLocal=null,也无法回收ThreadLocal对象。使用弱引用,当外层ThreadLocal=null时,Entry对象只包含ThreadLocal对象的虚引用,可以直接被GC回收。 当把ThreadLocal对象的引用置为null后,没有任何强引用指向内存中的ThreadLocal实例,threadLocals的key是它的弱引用,故它将会被GC回收。

但线程中threadLocals里的value却没有被回收,因为存在着一条从Entry对象连接过来的强引用,且因为无法再通过ThreadLocal对象的get方法获取到这个value,它永远不会被访问到了,所以还存在内存泄漏问题。
在这里插入图片描述

内存泄漏问题

在这里插入图片描述

每个thread中都存在一个map,map的类型的ThreadLocal,ThreadLocalMap,Map中的key为一个threadlocal的实例,这个map的确使用了弱引用,不过弱引用只是针对key,每个key都弱引用指向threadlocal。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal会被gc回收。但是,我们的value却不能被回收,因为存在一条从current thread连接过来的强引用(Entry数组中的Entry对象强引用value对象)。只有当前thread结束以后,current thread就不会存在栈中,强引用断开。Current Thread,Map,value将全部被gc回收。

只要这个线程对象被gc回收,就不会出现内存泄漏,但是在ThreadLocal设为null和线程结束这段时间不会被回收的。如果线程暂时不回收,这就发生了真正意义上的内存泄漏。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄漏。

Java为了最小化减少内存泄漏的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最坏的情况就是threadlocal对象设null了,开始发生内存泄漏,然后使用线程池。这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值