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对象就可以被释放了。

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

  • 相关阅读:
    支持nmap批量漏洞扫描的script
    Linux学习路径(小白必看)
    SEED实验系列:Collabtive系统SQL注入实验
    SEED实验系列:ShellShock 攻击实验
    SEED信息安全实验系列:缓冲区溢出漏洞实验
    SEED实验系列:缓冲区溢出漏洞试验
    信息安全不可错过的30门实验
    SEED实验系列文章目录
    Laravel大型项目系列教程(七)之7 扩展包和Artisan开发
    laravel大型项目系列教程(六)之优化、单元测试以及部署
  • 原文地址:https://www.cnblogs.com/stevenczp/p/8487595.html
Copyright © 2011-2022 走看看