Netty源码剖析之FastThreadLocal机制

news2024/11/26 4:41:01

版本信息:

JDK1.8

Netty-all:4.1.38.Final

传统的ThreadLocal机制

讲netty的FastThreadLocal机制,就不得不提及到JDK自带的ThreadLocal机制,所以下面会用一小段篇幅介绍一下ThreadLocal机制~

ThreadLocal的机制,大致的解释为线程本地变量,独属于线程,所以每个线程有一份,所以互相独立、隔离、线程安全、线程中任意地方可存可取。在Java中Thread类中存在一个集合用于实现此功能。

public class Thread implements Runnable {
    …………

    ThreadLocal.ThreadLocalMap threadLocals = null;

    …………
}

所以我们需要大致看一下如何使用,获取元素的源码如下: 

public T get() {
    // 获取到线程本地集合。
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);

    if (map != null) {
        // 根据ThreadLocal的Hash值得到集合的元素
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果没有线程本地集合,那就创建一个。
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
    // 根据Hash值来定位索引
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        // 直接命中,就返回
        return e;
    else
        // 如果冲突,往后面找
        return getEntryAfterMiss(key, i, e);
}

我们清楚,Hash运算虽然是O1时间复杂度,但是这只是最理想状态,因为无法避免Hash碰撞,当元素多起来,甚至能达到On的时间复杂度。而JDK原生的ThreadLocal就是通过Hash来计算索引下标,当发生Hash碰撞往后找临近节点插入,并不能达到理想的O1时间复杂度。

Netty中FastThreadLocal机制

上文介绍了JDK自带的ThreadLocal机制,他是根据Hash算法来存放节点,因为Hash碰撞的原因所以达不到O1的时间复杂度,而网传 " Netty的FastThreadLocal机制比它快3倍 " 所以下文介绍FastThreadLocal到底快在那里,如何实现的。

在这之前还需要介绍一个类 FastThreadLocalThread ,从上文我们知道JDK自带的ThreadLocal机制是通过Thread对象中ThreadLocal.ThreadLocalMap 集合存放数据,而 Netty的FastThreadLocal肯定也是需要有一个容器来存放数据,所以Netty设计了FastThreadLocalThread类,它实现与原生的Thread类,在其中定义了InternalThreadLocalMap集合。

public class FastThreadLocalThread extends Thread {

    …………

    private InternalThreadLocalMap threadLocalMap;

    …………
}

不妨看一下 InternalThreadLocalMap 类的定义,可以看到内部比较简单,实现UnpaddedInternalThreadLocalMap类,在UnpaddedInternalThreadLocalMap类中定义了一个数组用于存放FastThreadLocal的对象,还有兼容原生线程的ThreadLocal,以及一个原子类用于产出索引值。

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
}

class UnpaddedInternalThreadLocalMap {

    // 用于兼容,当线程非FastThreadLocalThread的情况下使用。
    static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
    
    // 用于原子性的产出索引值。
    // 注意这个变量是被static final修饰的
    static final AtomicInteger nextIndex = new AtomicInteger();

    // 存放FastThreadLocal管理的对象。
    Object[] indexedVariables;
}

铺垫做好了,接下来就看到FastThreadLocal类实现。

public class FastThreadLocal<V> {

    // 注意这里是static final修饰的,所以在clinit的时候初始化
    // InternalThreadLocalMap.nextVariableIndex() 这个方法是得到索引值
    // 所以这个值恒定为0,用作与特殊用途
    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

    // 索引值
    private final int index;

    public FastThreadLocal() {
        // 在构造方法中算出当前FastThreadLocal所属的索引值,而这个索引值用于InternalThreadLocalMap中。
        index = InternalThreadLocalMap.nextVariableIndex();
    }
}

public static int nextVariableIndex() {
    // 因为nextIndex是static修饰的,所以这里全局唯一。
    // 原子性自增,得到索引值。
    int index = nextIndex.getAndIncrement();
    if (index < 0) {
        nextIndex.decrementAndGet();
        throw new IllegalStateException("too many thread-local indexed variables");
    }
    return index;
}

在FastThreadLocal类中有一个索引值,此索引值在构造方法中初始化,目的是原子性的获取到当前FastThreadLocal的索引,而这个索引用途在于InternalThreadLocalMap 直接O1的时间复杂度找到元素。而因为每创建一个FastThreadLocal,UnpaddedInternalThreadLocalMap类中被static修饰的全局原子类索引值就会+1,所以就避免了重复的可能性。

 接下来,就看到如何使用FastThreadLocal,当然是通过get方法获取,获取不到就创建,然后放入集合中,下次就能够命中。这个跟ThreadLocal一模一样的思想。

public class FastThreadLocal<V> {

    public final V get() {
        // 取出线程中InternalThreadLocalMap集合
        // 当然必须是FastThreadLocalThread线程,如果是原生Thread对象的话,会使用原生ThreadLocal对象来兼容
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();

        Object v = threadLocalMap.indexedVariable(index);
        // 命中就直接返回
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }

        // 没命中就创建
        return initialize(threadLocalMap);
    }

    private V initialize(InternalThreadLocalMap threadLocalMap) {
        V v = null;
        try {
            // 模板方法,给子类实现。
            v = initialValue();
        } catch (Exception e) {
            PlatformDependent.throwException(e);
        }

        // 添加到线程对象的InternalThreadLocalMap集合中,下次就能够直接命中了。
        threadLocalMap.setIndexedVariable(index, v);

        // 把当前FastThreadLocal添加到移除队列中,当执行完毕后,Netty自动帮你回收空间。
        addToVariablesToRemove(threadLocalMap, this);
        return v;
    }

    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        
        // 拿到variablesToRemoveIndex索引的值,而variablesToRemoveIndex恒定为0。
        // 也即0号特殊用途。
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);

        Set<FastThreadLocal<?>> variablesToRemove;

        // 第一次是UNSET,所以去初始化
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            // 创建一个集合,用于存放已经使用的FastThreadLocal,后续用于自动资源回收
            variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
            threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
        } else {
            // 后续只需要取出来即可。
            variablesToRemove = (Set<FastThreadLocal<?>>) v;
        }

        // 把当前FastThreadLocal添加到集合中。
        variablesToRemove.add(variable);
    }
}

对以上FastThreadLocal的get方法做一个总结:

  1. 获取到FastThreadLocalThread线程的InternalThreadLocalMap集合,如果是原生的Thread线程就会使用ThreadLocal兼容InternalThreadLocalMap集合(所以,如果使用FastThreadLocal,不使用FastThreadLocalThread线程,那么效率会比原生的还低)
  2. 通过FastThreadLocal的索引从InternalThreadLocalMap集合中获取到元素
  3. 如果命中就直接返回
  4. 如果不命中就调用initialize做创建和初始化工作
  5. 创建工作调用initialValue方法,此方法是一个模板方法,交给子类使用,所以具体的创建交给子类
  6. 创建完毕后,添加到InternalThreadLocalMap集合中,下次就可以直接命中
  7. 最后会调用addToVariablesToRemove方法,此方法会把InternalThreadLocalMap的0号索引放入一个Set<FastThreadLocal<?>> 集合,用于后续的自动回收资源。

所以上述的图需要做出修正,因为InternalThreadLocalMap的0号索引有特殊用途。用于自动回收资源,后续马上介绍。

对于自动回收资源,其实实现的原理非常简单,我们看到FastThreadLocalThread的构造方法。这里使用了最经典的装饰者模式,把原有的Runnable给做了增强,当线程执行完run方法,也即执行完业务逻辑后,在finally代码块中做资源回收,所以也称之为自动回收资源。

public class FastThreadLocalThread extends Thread {
    public FastThreadLocalThread(Runnable target) {
        super(FastThreadLocalRunnable.wrap(target));
        cleanupFastThreadLocals = true;
    }
}

final class FastThreadLocalRunnable implements Runnable {

    private final Runnable runnable;

    @Override
    public void run() {
        try {
            runnable.run();
        } finally {
            // 资源回收
            FastThreadLocal.removeAll();
        }
    }

    static Runnable wrap(Runnable runnable) {
        return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
    }
}

最后再看一下,资源回收的代码逻辑。

public static void removeAll() {
    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
    try {
        // 获取到0号索引的set集合
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
        if (v != null && v != InternalThreadLocalMap.UNSET) {
            @SuppressWarnings("unchecked")
            Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
            FastThreadLocal<?>[] variablesToRemoveArray =
            variablesToRemove.toArray(new FastThreadLocal[0]);
            // 遍历set集合中存放的所有的FastThraedLocal对象。
            for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
                // 调用FastThraedLocal的remove方法。
                tlv.remove(threadLocalMap);
            }
        }
    } finally {
        InternalThreadLocalMap.remove();
    }
}

public final void remove(InternalThreadLocalMap threadLocalMap) {
    // 从集合中删除当前节点
    Object v = threadLocalMap.removeIndexedVariable(index);
    removeFromVariablesToRemove(threadLocalMap, this);

    if (v != InternalThreadLocalMap.UNSET) {
        try {
            // 模板方法,具体的回收资源细节,交给子类实现。
            onRemoval((V) v);
        } catch (Exception e) {
            PlatformDependent.throwException(e);
        }
    }
}

这里也非常的简单,就是拿到InternalThreadLocalMap 中0号索引对应的set集合,此集合中存放着所有的FastThreadLocal,然后一一调用FastThreadLocal的remove方法,而remove方法中调用了onRemoval方法,此方法是个标准的模板方法,具体的回收细节交给子类实现,父类只帮你开启自动回收机制,回收细节还是暴露给开发者~

总结:

  1. FastThreadLocal完全是O1时间复杂度,不过是空间换时间罢了,存在一定的内存浪费(尤其是项目中大量使用FastThreadLocal,每个线程的InternalThreadLocalMap会越来越大,浪费会越来越多,不过,如今内存不值钱,用很小很小一部分内存换取执行效率是值得的~!)
  2. FastThreadLocal一定要配合FastThreadLocalThread线程使用,不然快速特性完全使用不到,并且会降至使用原生ThreadLocal机制,最终导致效率比原生ThreadLocal机制还慢(因为存在一定的包装)
  3. FastThreadLocal虽然有自动回收资源机制,但是的子类还是需要实现onRemoval方法,回收的细节交给开发者,比如一些堆外内存的释放等等~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/922571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【云原生】Docker私有仓库 RegistryHabor

目录 1.Docker私有仓库&#xff08;Registry&#xff09; 1.1 Registry的介绍 1.2 Registry的部署 步骤一&#xff1a;拉取相关的镜像 步骤二&#xff1a;进行 Registry的相关yml文件配置&#xff08;docker-compose&#xff09; 步骤三&#xff1a;镜像的推送 2. Regist…

Unable to Locate package python2| Linux Ubuntu系统下python2的安装

Linux Ubuntu系统下python2的安装 FSL的安装脚本是用Python2写的&#xff0c;新版本的Ubuntu &#xff08;16以后&#xff09;在默认情况下没有安装Python2。在终端输入 python2&#xff0c;若提示没有相应的命令&#xff0c;则需要先安装Python2&#xff0c;如下指令&#xf…

如何把aac转化为mp3?大家和我一起往下学习

如何把aac转化为mp3&#xff1f;aac是一种先进的音频编码格式&#xff0c;通过较小的文件大小提供出色的音质体验。然而&#xff0c;由于其相对较少的普及度&#xff0c;与MP3相比&#xff0c;兼容性稍显不足&#xff0c;有些播放器可能无法直接识别aac格式。在某种程度上&…

ORB-SLAM2算法9之图像帧Frame

文章目录 0 引言1 Frame类1.1 构造和重载函数1.1.1 双目相机1.1.2 RGBD相机1.1.3 单目相机 1.2 成员函数1.2.1 特征点去畸变1.2.2 特征点网格分配1.2.3 双目匹配1.2.4 RGBD相机深度计算 1.3 成员变量 2 Frame类的用途 0 引言 ORB-SLAM2算法7详细了解了System主类和多线程和ORB…

Spring Boot(Vue3+ElementPlus+Axios+MyBatisPlus+Spring Boot 前后端分离)【一】

&#x1f600;前言 本篇博文是关于Spring Boot(Vue3ElementPlusAxiosMyBatisPlusSpring Boot 前后端分离)【一】&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章…

华为OD机试 - 过滤组合字符串 - 深度优先搜索dfs算法(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

共享数据-vue3

vuex方案 安装vuex4.x 两个重要变动&#xff1a; 去掉了构造函数Vuex&#xff0c;而使用createStore创建仓库 为了配合composition api&#xff0c;新增useStore函数获得仓库对象&#xff1b;获取路由对象使用useRouter global state 由于vue3的响应式系统本身可以脱离…

node.js 简单实验 创建一个简单的web服务

概要&#xff1a;用一个最简单是例子感受一下node.js 的能力 1.代码 var http require("http") http.createServer(function (request, response) { response.writeHead(200, {Content-Type: text/plain}); response.end(Hello World\n); }).listen(8081); cons…

浪潮信息企业级SSD:降本又增效?AIPR技术解决高并发读取性能大问题

NAND闪存作为一种非易失性存储介质&#xff0c;凭借其功耗低、重量轻、性能佳和断电后仍然能保存数据等特点&#xff0c;成为比硬盘驱动器更好的存储设备&#xff0c;非常适合作为便携设备的存储器来使用。 固态硬盘&#xff08;Solid State Disk&#xff0c;简称SSD&#xff0…

灯笼解算—kinfx

kinfx 刚刚开始学 做的可能比较复杂了。 skleton ——画的骨骼 rigpose 主要控制动态 sin((0.05*Framech(“…/Ctrol/ofset”))*ch(“…/Ctrol/freq”))*ch(“…/Ctrol/amp”) 用的简单的sin函数 变成实心 方便控制弯曲 原地做完 匹配ani位置 matrix mat point(1, transform…

智慧能源管理系统助力某制造企业提高能源利用效率

随着全球能源需求不断增加和能源价格的上涨&#xff0c;企业和机构日益意识到能源管理的重要性。传统的能源管理方式不仅效率低下&#xff0c;还容易造成资源浪费和环境污染。因此&#xff0c;许多企业开始探索采用智慧能源管理系统来提高能源利用效率&#xff0c;降低能源成本…

C#__自定义类传输数据和前台线程和后台线程

// 前台线程和后台线程 // 默认情况下&#xff0c;用Thread类创建的线程是前台线程。线程池中的线程总是后台线程。 // 用Thread类创建线程的时候&#xff0c;可以设置IsBackground属性&#xff0c;表示一个后台线程。 // 前台线程在主函数运行结束后依旧执行&#xff0c;后台线…

GNS3的使用

一、实验目的&#xff1a; 了解GNS3的使用方法&#xff0c;能够用GN3建立和模拟网络模型&#xff0c;并且将GNS3关联wireshark&#xff0c;进行抓包 二、预备知识和主要步骤&#xff1a; GNS3就是一个可以构建虚拟网络的软件 1&#xff0e;熟悉GNS3的界面&#xff0c;了解按键…

C#_多线程编程入门

字面理解&#xff1a;多个线程同时工作的过程。 案例① 单线程 #region ① 单线程做菜/// <summary>/// ① 单线程做菜:执行任务时,什么操作都动不了./// </summary>/// <param name"sender"></param>/// <param name"e">…

只需半分钟,ARMS 帮你配置出“高质量”告警

作者&#xff1a;图杨 背景 某位资深运维工程师A&#xff1a;“一天不收个几十条告警&#xff0c;我都觉得心里不踏实” 。运维工程师B&#xff1a;“我那几个告警天天告&#xff0c;我的应用一点问题都没有&#xff0c;但是我又不敢关”。运维工程师C&#xff1a;“我每天都…

安果天气预报 产品介绍

软件介绍版本号 2.0.5 安果天气预报&#xff1a;全世界覆盖&#xff0c;中国定制 想要查找北京、上海、纽约、东京还是巴黎的天气&#xff1f;一款简约的天气预 报应用为你呈现。专注于为用户提供纯净的天气体验&#xff0c;我们不发送任何打扰的通知。包含空气质量、能见度、…

使用Python搭建服务器公网展示本地电脑文件

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 Python作为热度比较高的编程语言&#xff0c;其语法简单且语句清晰&#xff0c;而且python有…

搜索树基础:二叉搜索树(详解特性用途,图解实现过程)

二叉搜索树 二叉搜索树的特性二叉搜索树的主要用途二叉搜索树的基本操作1、二叉搜索树的查找2、二叉搜索树的插入3、二叉搜索树的删除&#xff08;难点&#xff09;&#xff08;1&#xff09;找到待删结点&#xff08;2&#xff09;分情况删除 二叉搜索树的特性 二叉搜索树又称…

如何高效沟通避免沟通冲突 ?2大沟通漏斗模型

“昨天&#xff0c;我不是说这个Bug了&#xff1f;”“没有啊&#xff0c;你啥时候说的&#xff1f;”项目组成员沟通效率不高&#xff0c;往往造成沟通误解、争论不休&#xff0c;甚至出现沟通冲突&#xff0c;影响项目进度。 那么该如何提高沟通效率&#xff0c;避免不必要的…

Fmoc-D-Ser(Ac4-L-Manα)-OH主要采用的是Fmoc合成法,Fmoc合成法是以Fmoc为α-氨基的保护基

Fmoc-D-Ser(Ac4-L-Manα)-OH主要采用的是Fmoc合成法。Fmoc合成法是以Fmoc为α-氨基的保护基&#xff0c;配合侧链保护的苄醇类&#xff0c;完成多肽合成。 在西安凯新生物科技有限公司的Fmoc合成法的实施过程中&#xff0c;首先将一个Fmoc&#xff0d;氨基酸衍生物共价交联到树…