ThreadLocal

ThreadLocal 引入

在单线程应用程序中可能会维持一个全局的数据库连接,在程序启动时初始化这个连接对象,从而避免在调用每个方法时都要传递一个 Connection 对象

但是如果在多线程中没有同步的情况下使用全局共享变量就会存在线程安全问题,而 ThreadLocal 可以保证每个线程拥有属于自己的连接对象,相互独立

每个线程都有一片属于自己的独立内存空间,它是一个Map,底层是一个Entry[]数组,而每个Entry中包含了key & value,其中key就是ThreadLocal对象,而value就是设置的值

可能描述的比较模糊,直接来一个图:

1

当调用threadLocal.get()获取线程私有值时,首先会获得线程私有的ThreadLocalMap对象,然后以threadLocal为 key 获取到对应的Entry对象,最终获取对应的 value 值

ThreadLocal 数据结构

上一部分已经稍微介绍了一点 ThreadLocal 底层结构,这一部分从源码的角度详细介绍一波!!

首先每个线程都有一个自己的ThreadLocalMap对象,它是ThreadLocal的静态内部类:

接着看一下ThreadLocalMap的结构:

可以看出ThreadLocalMapHashMap有些许的相似,关于 HashMap 详细介绍可见 HashMap 源码剖析

但也有一些值得关注的点:弱引用 -> 指一些非必须的对象,但它比软引用强度更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生为止关于四种引用的详细介绍可见「强/软/弱/虚」引用

如果ThreadLocal对象只有Entry对它的一个弱引用,那么当 JVM 进行 GC 时会将ThreadLocal回收,如下图所示:

2

但此时value对象还存在,就出现了内存泄露。不过不要紧,因为后续会检测到 Entry 不为 null 但 key 为 null 的对象,然后将其清理掉,后续会介绍

回到上一部分的代码,思考一下threadLocal只有弱引用吗???显然不是,它还存在一个强引用!!!所以threadLocal对象并不会被 GC 清理

注意:ThreadLocal必须有且仅有弱引用时才会在 GC 时被清理

计算 ThreadLocal 对象的 HashCode

后文的源码分析会涉及到计算ThreadLocal对象的 HashCode,所以这里先来介绍一波~~

ThreadLocal类中有一个threadLocalHashCode变量记录着对象的 HashCode,主要从这里入手:

可以看到ThreadLocal对象计算 HashCode 的方式有些特别,不像传统的通过重写hashCode()方法,而是设置一个步长,当前对象 HashCode = Prev-HashCode + step

可以写一个代码验证一下:

从输出可以看出两个ThreadLocal对象的 HashCode 差值刚好是 0x61c88647 的倍数 (0x61c88647 的十进制为 1640531527)

至于为什么设置增长步长为 0x61c88647,是因为这样可以使计算得到索引分布的更均匀,减少哈希冲突

ThreadLocal.set()

下面开始在源码的世界畅游!!当直接调用set()方法时如下:

在分析ThreadLocalMap结构时可以发现Thread类并没有对它初始化,而是直接设置了一个 null 值。真正的初始化是在第一次调用set()方法时通过createMap()方法初始化:

回到set()中,如果map已经被初始化,那么将尝试将 (key, value) 插入 map 中:

在向后寻找处理哈希冲突时,如果遇到了失效元素会调用replaceStaleEntry()方法,它的逻辑比较复杂,单拎出来介绍:

该部分逻辑有点复杂,但我们只需要抓住几个判断的重点:

3

到此为止,介绍过的部分完成了两件事情:

所以下面开始介绍如何清理失效元素,从上面可以看出主要是cleanSomeSlots(expungeStaleEntry(slotToExpunge), len)来清理,下面就先看内层方法expungeStaleEntry()

下面来看看清理元素的外层方法cleanSomeSlots()

ThreadLocalMap 扩容机制

虽迟但到,有 Map 怎么会没有扩容机制呢??!!这不来了吗!!在ThreadLocalMapset()方法的最后两行,有一个关键性判断:

先来看看rehash()方法:

再来看看resize()方法:

ThreadLocal.get()

get相比于set就简单多了,直接看吧~~

可以看出,如果ThreadLocalMapThreadLocal对象为 key 的 value 值,那么就直接返回;如果没有,会向ThreadLocalMap中插入一个<ThreadLocal, null>的键值对,并返回 null

参考文章