美文网首页技术分享
ThreadLocal实现原理和最佳实践

ThreadLocal实现原理和最佳实践

作者: 十毛tenmao | 来源:发表于2019-12-05 23:35 被阅读0次

    ThreadLocal在多线程项目中使用很多,简化了多线程对资源的使用,

    ThreadLocal常用场景

    适用于每个线程需要有自己单独的实例,实例需要在多个方法中共享,但不希望被多线程共享

    • Web请求的用户身份态:Session
    • 请求的链路跟踪:traceId
    • SimpleDateFormat:因为SimpleDateFormat不是线程安全的

    ThreadLocal实现原理

    `ThreadLocal`内存布局

    图中实线是引用,虚线是弱引用(不会阻止内存的回收)

    • ThreadLocal中的数据实际上都是保存在Thread的成员变量ThreadLocal.ThreadLocalMap ThreadLocals中,ThreadLocalMap是一个Key是ThreadLocal`,Value是泛型T的一个Map
    • ThreadLocal本身并不存储值,只是作为一个ThreadLocalMap中的一个key
    • Hash冲突的解决方法:开放定址法(跟HashMap使用列表法和红黑树不同)

    ThreadLocal为什么会内存泄漏

    从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?

    我们先来看看官方文档的说法:

    To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
    为了应对非常大和长时间的用途,哈希表使用弱引用的 key。

    下面我们分两种情况讨论:

    • key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
    • key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
      比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

    因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
    因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

    自问自答

    • 为什么数据没有保存在ThreadLocal,而是Thread

      因为线程私有数据应该跟线程生命周期一致

    • 为什么Entry.value不使用WeakReference

      因为ThreadLocal可能还存在强引用,同时value没有外部的强引用,如果设置为WeakReference就可能导致ThreadLocal.get()拿不到对应的value了

    最佳实践

    • 使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。特别是在线程复用的场景,不但可以避免内存溢出,还避免了数据的错误复用

    参考

    相关文章

      网友评论

        本文标题:ThreadLocal实现原理和最佳实践

        本文链接:https://www.haomeiwen.com/subject/qdedgctx.html