0%

Java ThreadLocale vs Netty FastThreadLocale

背景介绍

在一些需要频繁使用某个参数的情况下,会使用ThreadLocale存储参数,这样只要不切换线程,或者在切换线程的时候做转移,就能随时随地访问该参数

Java ThreadLocale

每个Thread对象保存一个 ThreadLocalMap 变量,用map方式存储线程内的 ThreadLocale ,map的 key 是 ThreadLocale 对象,value是对应的值,具有以下特性:

  • 通过hash的方式存储,每创建一个新的ThreadLocale其hash值增加 0x61c88647,这是个神奇的数字,在不删除数据的情况下碰撞极少
  • 使用开地址法选择索引,即插入数据时,如果有index被占用,index + 1来向后查找可用空间(linear probe)
  • 当负载因子大于2/3时执行rehash,将数据挪到新的table
  • map 的 entry 使用 WeakReference,利好GC

上述特点展示了其一个弱点即慢,使用下面的代码测试就能发现

1
2
3
4
5
6
7
for (int i = 0; i < 1024; ++i) {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(i);
if (ThreadLocalRandom.current().nextDouble() > 0.5d) {
threadLocal.remove();
}
}

上述代码做了随机移除处理,因为在不移除的情况下,基本上是没有碰撞的,而存在移除的时候会产生碰撞,这些碰撞的key在后续查询的时候需要遍历多个槽,最坏情况是O(N)

Netty FastThradLocale

FastThreadLocale 主要在Netty框架代码中配合 FastThreadLocalThread 使用会更快,因为从 FastThreadLocalThread 获取 FastThreadLocale 是直接读一个field,相当于O(1),而普通的线程是从 ThreadLocale中读取(如果存在碰撞会慢),最坏时O(N)

FastThreadLocale 具备以下特点:

  • 采用AtomicInteger递增方式分配 index,使得查询能够满足O(1)
  • 删除数据只是将对应的index设置为UNSET,之后还会占用空间,属于典型的空间换时间

延伸

FastThreadLocale 中有几个padding变量,纯粹只是为了凑足64字节的倍数做 cache-line padding,解决 false sharing(伪共享问题),不会在同一个cache line中的其他值修改的时候重新加载,或者在自己重新加载的时候影响别的变量,适合这种有写入而且读取较频繁的场景

总结

目前使用 ThreadLocale 都是在最开始 set 数据进去,然后在后续使用过程中大量的set值,很少有中途删除再写入别的key的情况,这种情况下冲突较少,不会有太大影响,但是对于需要删除key的情况,Java 自带的 ThreadLocale 存在查询性能问题,需要考虑替换到 FastThreadLocale。

Reference

  1. Why Netty’s FastThreadLocal is fast
  2. 剖析Disruptor:为什么会这么快?
如果您觉得这些内容对您有帮助,你可以赞助我以提高站点的文章质量