深入剖析 Netty「基石」之 FastThreadLocal

 2023-01-24
原文作者:宁轩 原文地址:https://juejin.cn/post/7180130341963694117

前言

作为多线程重要的组成部分之一 ThreadLocal 应该是大家所熟悉的, 哪怕不熟悉学习Java的小伙伴也应该听说过, ThreadLocal是线程变量, 他以ThreadLocal对象为键, 任意对象为值的存储结构.

Netty中也有一个相似的类FastThreadLocal用来处理频繁访问的场景, 这个类弥补了ThreadLocal的诸多不足:

  • 效率低下
  • 容易出现Hash冲突
  • 需要O(n)是时间复杂度解决问题

接下来我们就一起学习一下FastThreadLocal

本篇文章将添加于我的Netty专栏 欢迎大家关注

InternalThreadLocalMap

InternalThreadLocalMap类似于ThreadLocalMap, 主要用于线程内部变量的存储

ThreadLocal的一个重要缺点就是ThreadLocalMap采用线性探测法解决Hash冲突问题, InternalThreadLocalMap的主要重要作用也是解决这个问题

get() 方法

  • 获取当前线程

    • 如果当前线程实现了FastThreadLocalThread类, 那么直接调用fastGet()方法获取到FastThreadLocalThreadthreadLocalMap属性
    • 如果当前线程没有实现FastThreadLocalThread类, 那么调用slowGet()方法

执行到slowGet()方法类似于一种对当前操作的兜底方案, 如果当前线程不是FastThreadLocalThread, 内部是没有InternalThreadLocalMap属性的, 此时获取就变成了获取 JDK 原生的 ThreadLocal

202212302125535301.png

FastThreadLocal

在方法适用性上, Netty做的非常好, 使用FastThreadLocal和使用 JDK 原生的ThreadLocal几乎没有区别, 方法基本保持一致

get() 方法

  • 首先去获取当前线程的ThreadLocalMap对象

  • 执行indexedVariable(idnex)方法, 从数组中取出下标为index的对象

  • 判断取出的对象是不是缺省对象

    • 是缺省对象UNSET, 说明这个位置已经填充过数据, 直接取出返回
    • 不是缺省对象UNSET, 进行初始化操作

202212302125540232.png

set()方法

  • 判断value是否为缺省值UNSET

    • 如果是缺省值, 调用remove()方法
    • 如果不是缺省值, 获取到当前的ThreadLocalMap, 然后调用 setKnownNotUnset(threadLocalMap, value) 方法, 将ThreadLocalMap中的数据替换为value

202212302125550463.png

setKnownNotUnset(threadLocalMap, value)

在这个方法中一共做了两件事:

  • 首先获取数组下标 index, 并在该下标设置新的 value

  • 如果成功

    • 则将当前的 FastThreadLocalThread(this) 保存到 Set 中

202212302125556564.png

setIndexedVariable(int index, Object value)

  • 在这里, 我们先去获取了当前的数组的长度, 判断下标和当前数组的容量大小

    • 如果容量不够则进行扩容方法
    • 如果容量充足, 找到数组index下标位置将新的value设置进去, 时间复杂度 O(1), 同时如果旧的数据是缺省的UNSET则返回 true

202212302125561405.png

下图是扩容方法详情图, 如果看过HashMap源码的同学应该会感觉比较熟悉, 他也是将容量向上取整为 2 次幂扩容, 然后将原数组内容拷贝到新数组

202212302125569736.png

HashMap扩容方法如下

202212302125577817.png

addToVariablesToRemove()

  • 获取下标为 0 的元素

  • 判断这个元素是否为缺省或者为 null

    • 如果是的话, 创建FastThreadLocal类型的Set集合, 将Set集合填充到数组下标为 0 的位置
    • 如果不是的话, 代表Set集合已存在, 直接强制转换获得Set集合
  • FastThreadLocal添加到Set集合中

202212302125585288.png

总结

FastThreadLocal我们就学到这里了

关于这个类的高效查找我们也看到了是因为 JDK 原生的 ThreadLocal是 Hash 查找 ,而FastThreadLocal是 index 查找, 高效的同时避免了 Hash 冲突

本文内容到此结束了

如有收获欢迎点赞?收藏?关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问?欢迎各位大佬指出。

我是 宁轩 , 我们下次再见