Java 8 ThreadLocal 源码解析
原文几处备注如下:
- 原文写到:
源码解析
我们从
ThreadLocal.set
方法开始分析:
....每个 Thread 对象维护了一个 ThreadLocalMap 类型的 threadLocals 字段。
ThreadLocalMap 的 key 是 ThreadLocal 对象, 值则是变量的副本, 因此允许一个线程绑定多个 ThreadLocal 对象。
这里我自己解释下:
ThreadLocalMap内部维护了一个名为table的 Entry[]数组实例变量,而Entry类是定义在ThreadLocalMap类内部的静态实例内部类。Entry类的键(Key)是ThreadLocal,值(Value)是Object类型。所以说 因此允许一个线程绑定多个 ThreadLocal 对象。
- 原文写到
为了处理GC造成的空洞(stale entry), 需要调用expungeStaleEntry
方法进行清理。
此处的空洞(stale entry)相当于脏entry,具体情况如下:
Entry e!=null && ThreadLocal<?> k = e.get() 中的 k == null。
因为Entry类对象是 持有实例referent 为 ThreadLocal<?>类型的WeakReference对象
所以key已经被gc回收了,replaceStaleEntry方法就是用来解决内存泄露问题。
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清理当前的空洞
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 若下一个位置还是空洞将其一并清除
if (k == null) { //hit it !
e.value = null;
tab[i] = null;
size--;
} else {
// 发现不是空洞的 Entry 将其放入最靠前的哈希槽中
int h = k.threadLocalHashCode & (len - 1);
if (h != i) { //h != i说明有过hash冲突 --- 备注【1】
tab[i] = null;
while (tab[h] != null) // 处理移动过程中的哈希冲突
h = nextIndex(h, len);
tab[h] = e;
}
}
/**循环执行直到遇到空的哈希槽, 表明从 staleSlot 开始的查找序列中间(从 staleSlot 开始的聚集或者从 staleSlot 开始的一次探测)不会存在空哈希槽或空Entry*/
}
return i;
}
备注【1】:
Java数据结构与算法第二版 P412
冲突:
不可避免把几个不同的单词哈希化到同一个数组单元,至少偶尔会这样。当然希望每个数组下标对应一个数据项,但是通常这不可能。只能寄希望于没有太多的单词有同样的数组下标。
假设要在数组中插入单词melioration。通过哈希函数得到了它的数组下标后,发现那个单元已经有了一个单词(demystify)了,因为这个单词哈希化后得到的数组下标与melioration相同(对一个特定大小的数组)。这种情况称为冲突。
- 注意看JDK关于 ThreadLocalMap类的expungeStaleEntry()方法源码的注释如下(见红字标注):
3.原文写道;
查看代码
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) { /**3 哈希槽中为null, 说明搜索结束未找到目标*/
ThreadLocal<?> k = e.get();
if (k == key) /**1 哈希槽中是当前ThreadLocal, 说明找到了目标*/
return e;
if (k == null)
/**4 哈希槽中存在Entry, 但是 Entry 中没有 ThreadLocal 对象。因为 Entry 使用弱引用, 这种情况说明 ThreadLocal 被GC回收。所以,将过期的key清除掉,并把后面的元素(移动过位置的)往前移*/
expungeStaleEntry(i);
else /**2 哈希槽中为其它ThreadLocal, 需要继续查找 */
i = nextIndex(i, len); /**往后移一位**/
e = tab[i];
}
return null; /**3 哈希槽中为null, 说明搜索结束未找到目标*/
}
getEntryAfterMiss 方法会循环查找直到找到或遍历所有可能的哈希槽, 在循环过程中可能遇到4种情况:
- 1 哈希槽中是当前ThreadLocal, 说明找到了目标
- 2 哈希槽中为其它ThreadLocal, 需要继续查找
- 3 哈希槽中为null, 说明搜索结束未找到目标
- 4 哈希槽中存在Entry, 但是 Entry 中没有 ThreadLocal 对象。因为 Entry 使用弱引用, 这种情况说明 ThreadLocal 被GC回收。
这里解释一下为什么“哈希槽中为null,说明搜索结束”---参考 Java数据结构和算法中文版(第二版)P414
Find按钮
Find按钮对键入的关键字值应用哈希函数。结果是一个数组下标。这个下标所指单元可能是要寻找的关键字;这是最好的情况,并且立即报告查找成功。
然而,也可能这个单元被其他关键字占据。这就是冲突;会看到红色箭头指向被占用的单元。根据冲突的位置,查找算法依次查找下一个单元。查找合适单元的过程叫做探测。
根据冲突的位置,查找算法只是沿着数组一个一个地察看每个单元。如果在找到要寻找的关键字前遇到一个空位,说明查找失败。不需要再做查找,因为插入算法本应该把这个数据项插在那个空位上(如果不是前面那样的话)。图11.7显示了成功和不成功的线性探测。
其他参考文章:
Java多线程与并发之ThreadLocal
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述