FastThreadLocal

前面介绍过 JDK 的 ThreadLocal , 使用不当的话容易造成内存泄漏最终导致OOM, 并且也有一些地方设计的不够好(相对于接下来要介绍的 FastThreadLocal), 接下来我们就介绍一下 Netty 改进的 FastThreadLocal, 看它到底 Fast 在哪里.

(JDK 的 ThreadLocal 的地址: https://www.cnblogs.com/wuhaonan/p/11427119.html)

同样的, 这回我们根据 FastThreadLocal 的源码对其进行分析.

FastThreadLocal#构造方法

FastThreadLocal 有一个标记自己下标的 index , 表明当前 FastThreadLocal 在 InternalThreadLocalMap 存储数据的数组中(Object[] indexedVariables)所处的下标.

	// 位于 map 中的下标
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}

跟踪 InternalThreadLocalMap.nextVariableIndex(); 的实现可以看到:

	public static int nextVariableIndex() {
// nextIndex 见下面
int index = nextIndex.getAndIncrement();
// 整数的最大值+1就变成了负数, 不过一般也不会用这么多的 ThreadLocal
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
// 这是个原子变量, 可以根据这个变量获取当前 FastThreadLocal 下标, 因为这是递增的(nextIndex.getAndIncrement()), 所以不会出现多个 FastThreadLocal 下标相同, 即 FastThreadLocal 的下标唯一.
static final AtomicInteger nextIndex = new AtomicInteger();

//todo: 扩容的时候, ThreadLocal 根据 hash 值取余长度计算下标, 可能会导致下标冲突, 需要循环往后查找空的位置放置. FastThreadLocal 直接复制以前的部分, 扩容出来的直接设置初始值, 不用加多一层循环去判断是否为空(可以设置进去), 这就是 唯一的 index 的好处, 不会导致冲突.

FastThreadLocal#set()

	public final void set(V value) {
if (value != InternalThreadLocalMap.UNSET) {
// 获取当前线程的 threadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
// 如果是新添加进来的话,则需要注册一个清理器
if (setKnownNotUnset(threadLocalMap, value)) {
// 注册清理器
registerCleaner(threadLocalMap);
}
} else {
remove();
}
}
private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
// 返回true的话表示是新添加的 ThreadLocal
if (threadLocalMap.setIndexedVariable(index, value)) {
// 则添加进需要 remove 的 集合(set)中
addToVariablesToRemove(threadLocalMap, this);
return true;
}
return false;
}
// 根据下标设置值, index 为 FastThreadLocal 的 唯一index
public boolean setIndexedVariable(int index, Object value) {
// 获取到所有存储的 FastThreadLocal
Object[] lookup = indexedVariables;
// 下标越界判断
if (index < lookup.length) {
Object oldValue = lookup[index];
lookup[index] = value;
// 只有添加了新的 ThreadLocal 才会返回 true
return oldValue == UNSET;
} else {
// 超过了 map 的大小则进行扩容,扩容见后面的代码
expandIndexedVariableTableAndSet(index, value);
return true;
}
}

可以看到, FastThreadLocal#set 的时候直接根据原子变量获取最新的 index , 然后直接设置进去.

FastThreadLocal#get()

get的过程比较简单,就不多赘述了

    public final V get() {
// 当前 thread 的 threadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
// value 值根据 new 的时候的 index 来获取
Object v = threadLocalMap.indexedVariable(index);
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
} V value = initialize(threadLocalMap);
registerCleaner(threadLocalMap);
return value;
}

InternalThreadLocalMap#expandIndexedVariableTableAndSet

这是 ThreadLocalMap 中的一个扩容方法,一共有3步操作:

1.申请一个新数组,大小为原来的两倍

2.copy数据到新数组(浅拷贝)并且初始化新增部分

3.设置map中新的数组

    //  对 Object[] 进行扩容
private void expandIndexedVariableTableAndSet(int index, Object value) {
// 旧的 Object[]
Object[] oldArray = indexedVariables;
final int oldCapacity = oldArray.length;
// index * 2
int newCapacity = index;
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity ++; // 进行浅拷贝
Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
// 初始化后面新申请的元素 newArray[ oldCapacity , newArray.length )
Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
// 设置刚加进来的值
newArray[index] = value;
// 设置新数组
indexedVariables = newArray;
}

接下来看一下 JDK 中的 ThreadLocal 中的扩容方法, 也把它当成三步来看吧

1.申请新数组,大小为原来的两倍

2.将数据放入新的数组( hash % (newLen -1))

3.设置map中新的数组

几个步骤看起来是差不多, 主要的不同就是在第二步, JDK 中计算下标的位置是 hash % (newLen -1) , 用的是 hash值取余, 会出现冲突, 就像 HashMap 从头节点一直找到链表的最后一个节点(如果是树的话就找到相应大小的地方), 冲突后就循环查找, 这里就是 JDK 的 ThreadLocal 耗时的地方.

  private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
// double size
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0; for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
// 这里是数据位于数组中的下标
int h = k.threadLocalHashCode & (newLen - 1);
// 直到找到空的Entry, 才设置进去, 如果原来的 Entry 已经有了, 需要一直循环往后查找空的位置
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
} setThreshold(newLen);
size = count;
table = newTab;
}

Netty 的 FastThreadLocal 并不会像 JDK 的 ThreadLocal 那样会出现下标冲突和循环里查找, 这是 FastThreadLocal --> Fast 的其中重要原因.

最后

这次的内容到这里就结束了,最后的最后,非常感谢你们能看到这里!!你们的阅读都是对作者的一次肯定!!!

觉得文章有帮助的看官顺手点个赞再走呗(终于暴露了我就是来骗赞的(◒。◒)),你们的每个赞对作者来说都非常重要(异常真实),都是对作者写作的一次肯定(double)!!!

Netty学习(四)FastThreadLocal的更多相关文章

  1. Netty学习(四)-TCP粘包和拆包

    我们都知道TCP是基于字节流的传输协议.那么数据在通信层传播其实就像河水一样并没有明显的分界线,而数据具体表示什么意思什么地方有句号什么地方有分号这个对于TCP底层来说并不清楚.应用层向TCP层发送用 ...

  2. Netty学习四:Channel

    1. Channel Channel是Netty的核心概念之一,它是Netty网络通信的主体,由它负责同对端进行网络通信.注册和数据操作等功能. 1.1 工作原理 如上图所示: 一旦用户端连接成功,将 ...

  3. Netty 学习(四):ChannelHandler 的事件传播和生命周期

    Netty 学习(四):ChannelHandler 的事件传播和生命周期 作者: Grey 原文地址: 博客园:Netty 学习(四):ChannelHandler 的事件传播和生命周期 CSDN: ...

  4. Netty 学习(十):ChannelPipeline源码说明

    Netty 学习(十):ChannelPipeline源码说明 作者: Grey 原文地址: 博客园:Netty 学习(十):ChannelPipeline源码说明 CSDN:Netty 学习(十): ...

  5. Netty学习之客户端创建

    一.客户端开发时序图 图片来源:Netty权威指南(第2版) 二.Netty客户端开发步骤 使用Netty进行客户端开发主要有以下几个步骤: 1.用户线程创建Bootstrap Bootstrap b ...

  6. Netty 学习笔记(1)通信原理

    前言 本文主要从 select 和 epoll 系统调用入手,来打开 Netty 的大门,从认识 Netty 的基础原理 —— I/O 多路复用模型开始.   Netty 的通信原理 Netty 底层 ...

  7. Netty学习第一节Netty的总体概况

    一.Netty简介 什么是Netty? 1.高性能事件驱动,异步非阻塞的IO加载开源框架. 它是由JBoss提供,用于建立TCP等底层链接.基于Netty可以建立高性能的HTTP服务器,快速开发高性能 ...

  8. Netty 学习(二):服务端与客户端通信

    Netty 学习(二):服务端与客户端通信 作者: Grey 原文地址: 博客园:Netty 学习(二):服务端与客户端通信 CSDN:Netty 学习(二):服务端与客户端通信 说明 Netty 中 ...

  9. netty学习资料

    netty学习资料推荐官方文档和<netty权威指南>和<netty in action>这两本书.下面收集下网上分享的资料 netty官方参考文档 Netty 4.x Use ...

随机推荐

  1. k8s补充

    k8s补充 容器云发展及主要内容 1.云计算,交付标准(iaas--openstack) 国内:阿里云一华为云(振兴杯)百度云(私有云) 国外:AWS 2.平台即服务(PAAS) 例如:新浪云(号称免 ...

  2. 基于Socket实现多人聊天室

    当前支持: 1.仅文字 2.加入聊天室提醒 3.退出聊天室提醒 可能出现的BUG: 1.可能出现客户端发送信息后不能及时推送,需要下一个客户端发送信息后一起推送 服务端代码: 1 package co ...

  3. Docker入门的亿点点学习

    前段时间花了些时间学习了亿点点docker,也算是入门了吧,顺便记了一下笔记拿出来分享给想要接触docker的兄弟们. 没有服务器的兄嘚可以去腾讯云或者阿里云领取免费的试用产品嗷,如果已经领取过了,又 ...

  4. 帆软报表(finereport)鼠标悬停背景变色

    在报表中,为了突出鼠标所在单元格,当鼠标悬浮时突出背景色(字体),鼠标离开后恢复原有的背景色(字体). 在设计器 模板>模板 Web 属性>填报页面设置,去除填报当前编辑行背景设置的勾选, ...

  5. HTML基础笔记整理

    「学习笔记」HTML基础 前言 勤做笔记不仅可以让自己学的扎实,更重要的是可以让自己少走弯路.有人说:"再次翻开笔记是什么感觉",我的回答是:"初恋般的感觉". ...

  6. 01网络编程(基础知识+OSI七层协议+TCP与UDP)

    目录 01 网络编程 一.软件开发架构 1.1 CS架构 1.2 BS架构 二.网络理论前戏 2.1 简介 2.2 常见硬件 三.OSI七层协议(五层) 3.1 七层协议 3.2 五层协议 3.3 知 ...

  7. python中继承的语法及案列

    案例: 1 class Chinese: # 类的创建,类名首字母大写 2 eye = 'black' # 类属性的创建 3 4 def eat(self): # 实例方法创建 5 print('吃饭 ...

  8. 基于C#打造的OPCUA客户端应用

    OPC UA (Unified Architecture),是工业4.0的标准通信规范,大家现在都不陌生. 目前大部分工控行业的应用系统都逐渐的在向OPC UA靠拢,所以随着iot的发展,OPC UA ...

  9. node / npm安装、启动报错

    1. 系统禁止运行脚本 a. 在系统中找到Windos PowerShell[可以按win健,然后搜powershell]--以管理员身份打开 b. 在打开的窗口输入set-ExecutionPoli ...

  10. 软件性能测试分析与调优实践之路-Java应用程序的性能分析与调优-手稿节选

    Java编程语言自从诞生起,就成为了一门非常流行的编程语言,覆盖了互联网.安卓应用.后端应用.大数据等很多技术领域,因此Java应用程序的性能分析和调优也是一门非常重要的课题.Java应用程序的性能直 ...