背景介绍
在一些需要频繁使用某个参数的情况下,会使用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 | for (int i = 0; i < 1024; ++i) { |
上述代码做了随机移除处理,因为在不移除的情况下,基本上是没有碰撞的,而存在移除的时候会产生碰撞,这些碰撞的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。