Netty学习(四)FastThreadLocal
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的更多相关文章
- Netty学习(四)-TCP粘包和拆包
		
我们都知道TCP是基于字节流的传输协议.那么数据在通信层传播其实就像河水一样并没有明显的分界线,而数据具体表示什么意思什么地方有句号什么地方有分号这个对于TCP底层来说并不清楚.应用层向TCP层发送用 ...
 - Netty学习四:Channel
		
1. Channel Channel是Netty的核心概念之一,它是Netty网络通信的主体,由它负责同对端进行网络通信.注册和数据操作等功能. 1.1 工作原理 如上图所示: 一旦用户端连接成功,将 ...
 - Netty 学习(四):ChannelHandler 的事件传播和生命周期
		
Netty 学习(四):ChannelHandler 的事件传播和生命周期 作者: Grey 原文地址: 博客园:Netty 学习(四):ChannelHandler 的事件传播和生命周期 CSDN: ...
 - Netty 学习(十):ChannelPipeline源码说明
		
Netty 学习(十):ChannelPipeline源码说明 作者: Grey 原文地址: 博客园:Netty 学习(十):ChannelPipeline源码说明 CSDN:Netty 学习(十): ...
 - Netty学习之客户端创建
		
一.客户端开发时序图 图片来源:Netty权威指南(第2版) 二.Netty客户端开发步骤 使用Netty进行客户端开发主要有以下几个步骤: 1.用户线程创建Bootstrap Bootstrap b ...
 - Netty 学习笔记(1)通信原理
		
前言 本文主要从 select 和 epoll 系统调用入手,来打开 Netty 的大门,从认识 Netty 的基础原理 —— I/O 多路复用模型开始. Netty 的通信原理 Netty 底层 ...
 - Netty学习第一节Netty的总体概况
		
一.Netty简介 什么是Netty? 1.高性能事件驱动,异步非阻塞的IO加载开源框架. 它是由JBoss提供,用于建立TCP等底层链接.基于Netty可以建立高性能的HTTP服务器,快速开发高性能 ...
 - Netty 学习(二):服务端与客户端通信
		
Netty 学习(二):服务端与客户端通信 作者: Grey 原文地址: 博客园:Netty 学习(二):服务端与客户端通信 CSDN:Netty 学习(二):服务端与客户端通信 说明 Netty 中 ...
 - netty学习资料
		
netty学习资料推荐官方文档和<netty权威指南>和<netty in action>这两本书.下面收集下网上分享的资料 netty官方参考文档 Netty 4.x Use ...
 
随机推荐
- String类为什么可以直接赋值
			
在研究String直接赋值与new String的区别之前我们需要先了解java中的字符串常量池的概念 字符串常量池 String类是我们平常项目中使用频率非常高的一种对象类型,jvm为了提升性能和减 ...
 - hibernate中的映射文件xxx.hbm.xml详解总结
			
转自 http://blog.csdn.net/a9529lty/article/details/6454924 一.hibernate映射文件的作用: Hibernate映射文件是Hibernate ...
 - Spring Cloud Alibaba Nacos路由策略之保护阈值!
			
在 Nacos 的路由策略中有 3 个比较重要的内容:权重.保护阈值和就近访问.因为这 3 个内容都是彼此独立的,所以今天我们就单独拎出"保护阈值"来详细聊聊. 保护阈值 保护阈值 ...
 - 帆软报表(finereport)JS实现cpt中详细单元格刷新
			
1.刷新固定单元格 setInterval(function(){ //获取第二行第 5 列 E2 单元格对象 var _changeCell = $("tr[tridx=1]" ...
 - CSRF跨站请求伪造漏洞分析
			
CSRF 现在的网站都有利用CSRF令牌来防止CSRF,就是在请求包的字段加一个csrf的值,防止csrf,要想利用该漏洞,要和xss组合起来,利用xss获得该csrf值,在构造的请求中将csrf值加 ...
 - 基于zynq XC7Z100 FMC接口通用计算平台
			
1.板卡概述 此板卡是北京太速研发,由SoC XC7Z100-2FFG900I芯片来完成卡主控及数字信号处理,XC7Z100内部集成了两个ARM Cortex-A9核和一个kintex 7的FPGA, ...
 - .NET 云原生架构师训练营(权限系统 系统演示 EntityAccess)--学习笔记
			
目录 模块拆分 EntityAccess 模块拆分 EntityAccess 实体权限 属性权限 实体权限 创建 student https://localhost:7018/Student/dotn ...
 - Python实例:贪吃蛇(简单贪吃蛇编写)🐍
			
d=====( ̄▽ ̄*)b 叮~ Python -- 简易贪吃蛇实现 目录: 1.基本原理 2.需要学习的库 3.代码实现 1.基本原理 基本贪吃蛇所需要的东西其实很少,只需要有一块让蛇动的屏幕, 在 ...
 - CobaltStrike逆向学习系列(8):Beacon 结果回传流程分析
			
这是[信安成长计划]的第 8 篇文章 关注微信公众号[信安成长计划] 0x00 目录 0x01 Beacon 接收与处理 0x02 结果回传 Beacon 在接受完命令并执行后,会将数据加密回传给 T ...
 - windev的字符集选择设置及元素命名方法建议
			
windev支持多语言,且支持整站翻译,同时支持最终用户的多语言选择,可以说多语言功能已经非常的全面和强大. windev原生支持英语.法语和葡萄牙语,在使用如中文等非拉丁字母语言时,需要在多个地方进 ...