zoukankan      html  css  js  c++  java
  • ThreadLocal深度解析

    本文基于jdk1.8.0_66写成

    0. ThreadLocal简介

    ThreadLocal可以提供线程内的局部对象,合理的使用可以避免线程冲突的问题
    比方说SimpleDateFormat是线程不安全的,但是如果用ThreadLocal给每个线程分配一个SimpleDateFormat对象,我们就可以安全的使用了

    为了便于理解,我们可以将ThreadLocal想象成一个Map,key为当前线程,value为存入的值
    threadLocal.get() 等效于 map.get(Thread.currentThread())
    threadLocal隐式的帮我们完成了获取当前线程的操作,使用起来更为方便

    1. naive的想法

    如同简介里所说,ThreadLocal最直接的设计思路是:
    在ThreadLocal内部维护一个Map,key为当前线程,value为存入的值
    ThreadLocal.set(obj)等效于Map.set(Thread.currentThread(), obj)
    ThreadLocal.get()等效于Map.get(Thread.currentThread())

    这个想法的优点是实现简单,但是问题也很多:

    a. 一般来说,一个ThreadLocal会与多个Thread关联,而一个Thread只会与少数的ThreadLocal关联。所以从ThreadLocal去寻找关联的Thread,开销比从Thread寻找关联的ThreadLocal要大。
    b. 如果一个Thread死掉了,为了防止内存泄漏,所有ThreadLocal中与这个Thread关联的value都要被释放,这个过程是手动的,不优雅,而且开销较大。

    2. JDK1.8中的想法

    每个Thread各自维护一个名为threadLocals的变量,其类型为ThreadLocal.ThreadLocalMap
    这个Map的key是ThreadLocal,value是关联的值
    在调用ThreadLocal.get/set时,先用Thread.currentThread()获得当前Thread,然后找到当前Thread的threadLocals域,再以当前ThreadLocal为key找到对应的value并返回。

    这样做好处很多:

    a. 一般来说,一个Thread只会与少数的几个ThreadLocal关联,那么从Thread去寻找对应的ThreadLocal开销是很小的
    b. 如果一个Thread死掉了,那么它所关联的threadLocals也会被自动释放,在很大程度上避免了内存泄漏的问题


    3. Netty中的进一步改进

    Netty自定义了一个名为FastThreadLocal的东西
    大概想法:
    原版ThreadLocal中,在ThreadLocal.ThreadLocalMap中查找时,采用的是线性探测法,发生哈希碰撞时会导致查询变慢
    为了避免这一问题,Netty为每个FastThreadLocal都设置了独一无二的编号,Thread可以直接根据这个编号寻址
    这样做绝对不会有哈希碰撞,但是占用的空间也相应变大了,也就是空间换时间的套路。


    4. ThreadLocal与内存泄漏

    ThreadLocal有个很微妙的地方在于,它在某些场景下,还是会发生内存泄漏

    如果我们在函数里定义了一个局部的ThreadLocal变量,主线程往里面set了一个很大的对象Huge后就退出这个函数该干嘛干嘛去了
    现在这个ThreadLocal连带着Huge都是垃圾了,但是gc能回收他们吗?

    按照一般的思路,ThreadLocal和Huge都会被Thread自带的threadLocals引用,所以都不会被回收。
    但是JDK的作者比我机智很多了,他们把ThreadLocal.ThreadLocalMap.Entry弄成了弱引用(WeakReference),也就是说没有引用的ThreadLocal对象是会在full gc中被回收的。
    但是问题依然存在:虽然ThreadLocal被回收了,但是它关联的Huge对象却还在,这可如何是好?

    JDK的作者此时又玩了个骚操作,在对ThreadLocal做set操作时,会去检查ThreadLocal.ThreadLocalMap的底层数组,如果发现某个key是null了(ThreadLocal被gc了),它会把对应的value也设为null,这样Huge对象就可以被释放了。

    但是为了性能考虑,这个检查操作不会遍历整个底层数组,而是每次只扫描一小段,所以在某些特定的场景下,还是会发生内存泄漏的。

  • 相关阅读:
    Nodejs下载和第一个Nodejs示例
    永久关闭Win10工具栏的TaskbarSearch控件
    对称加密,非对称加密,散列算法,签名算法
    【转】TTL和RS232之间的详细对比
    zlg核心板linux系统中查看系统内存等使用信息
    Power BI后台自动刷新数据报错 The operation was throttled by Power BI Premium because there were too many datasets being processed concurrently.
    剪切板和上传文件内容获取
    CSS, LESS, SCSS, SASS总结
    文字程序
    electron 打包“ERR_ELECTRON_BUILDER_CANNOT_EXECUTE”
  • 原文地址:https://www.cnblogs.com/stevenczp/p/8487595.html
Copyright © 2011-2022 走看看