threadlocal原理

底层原理

实际上是每个Thread线程维护着一个ThreadLocalMap,ThreadLocalMap底层是一个带有key,value的Entry数组,而Entry的键即是一个ThreadLocal对象,value是我们通过ThreadLocal.set()方法传进去的参数
ThreadLocal.java:
一个ThreadLocal对象只能存储一个value
在这里插入图片描述
在这里插入图片描述
Thread.java:
在这里插入图片描述
threadlocal.ThreadlocalMap.java:
在这里插入图片描述

threadlocal.ThreadlocalMap.Entry.java:
在这里插入图片描述
整体结构:
注意,一个线程里如果设置了多个ThreadLocal对象,那么他们在同一个ThreadLocalMap里
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value
在这里插入图片描述

内存泄漏

为什么ThreadLocal是弱引用

准确来说,是ThreadLocal.ThreadLocalMap.Entry是弱引用对象,它持有了对threadlocal对象的弱引用。
在这里插入图片描述
ThreadLocal在使用的时候,存在两条引用链:
1.业务代码中创建的ThreadLocal对象的强引用
2.线程Thread中的ThreadlocalMap的Entry是弱引用
使用弱引用,这样在业务代码置ThreadLocal为null或者ThreadLocal在使用结束后,因为是弱引用Entry引着ThreadLocal,那么ThreadLocal可以被回收,但是Entry还在的

内存泄漏的原因

那么也就是说,ThreadLocal对象我们程序用完了由于从 程序->ThreadLocal 这条强引用链不存在了,而线程本身->ThreadLocalMap->Entry->ThreadLocal 这条链又是一条弱引用链,那么GC的时候ThreadLocal就会被回收了。但是使用ThreadLocal对象作为key的这些Entry本身是不会被清除的,为什么呢?因为存在一条强引用链。即线程本身->ThreadLocalMap->Entry,而有由于作为Entry的key对象的ThreadLocal被回收了,这样就会在ThreadLocalMap中存在一些key为null的Entry。因为key变成null了,我们是没法访问这些Entry的,但由于Entry中还保存着value,value可能还占用着一大块内存,那么这部分内存就相当于内存泄漏了,因为他占用着内存没法回收,而我们又没法访问到它。
理论上,这条线程结束,这部分内存就会被回收,但是我们在使用线程池的时候,线程使用完了是会放回到线程池循环使用的。由于ThreadLocalMap的生命周期和线程一样长,如果没有手动删除对应key就会导致这块内存即不会回收也无法访问,也就是内存泄漏

ThreadLocal的补救

其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。但是这些举动不能保证内存就一定会回收,因为可能这条线程被放回到线程池里后再也没有使用,或者使用的时候没有调用其get(),set(),remove()方法
内存泄漏归根结底是由于ThreadLocalMap的生命周期跟Thread一样长

如何避免内存泄漏

每次使用完ThreadLocal,都调用它的remove()方法,清除数据

应用

建一个threadloca管理类,静态方法存取值即可。
可能有人有疑问:ThreadLocal对象是类的静态属性,那多个线程都用同一个属性不是有冲突吗?
别忘了,ThreadLocal这个属性是一个key而已,但是map在不同的线程,他们存取在不同的Thread,不会相互影响

public class UserContext implements Serializable {

    private static ThreadLocal<AccountDto> accountDtoThreadLocal = new ThreadLocal<>();

    public static AccountDto getAccountDto() {
        Thread a = Thread.currentThread();
        System.out.println(a.getName());

        return accountDtoThreadLocal.get();
    }

    public static void setAccountDto(AccountDto accountDto) {
        Thread a = Thread.currentThread();
        System.out.println(a.getName());
        accountDtoThreadLocal.set(accountDto);
    }

    public static void removeUserSession() {
        accountDtoThreadLocal.remove();
    }

}

参考:https://blog.csdn.net/qq32933432/article/details/80212873

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值