当前位置: 首页 > news >正文

聊一聊Netty中的执行器选择策略

前言

在前面的一篇博文中有写到:当执行器组EventLoopGroup在执行register(Channel channel)方法注册通道时,首先后会使用next()方法从执行器组中选择一个执行器来执行。至于next()方法如何去选择一个执行器,就是本文要聊的东东。

Netty的执行器选择器工厂与选择器

在NioEventLoopGroup实例化过程中,会调用其父类的MultithreadEventExecutorGroup构造方法,如果没有传入选择器工厂,则使用默认的DefaultEventExecutorChooserFactory。

MultithreadEventExecutorGroup中的构造方法:

    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
        //使用DefaultEventExecutorChooserFactory对象作为选择器工厂
        //继续调用其他构造方法
        this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
    }

其中DefaultEventExecutorChooserFactory.INSTANCE返回的是一个单例模式下的DefaultEventExecutorChooserFactory对象。

public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {

    //默认的执行器选择器工厂
    public static final DefaultEventExecutorChooserFactory INSTANCE = new DefaultEventExecutorChooserFactory();
    /*
    *  省略其他代码
    */
}

跟进MultithreadEventExecutorGroup的另一个构造方法中:
(省略了大量与本文内容无关的代码)

//这里传入的chooserFactory就是上面的构造方法中说的DefaultEventExecutorChooserFactory对象
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
                                            
        checkPositive(nThreads, "nThreads");
        //创建执行器数组,存储执行器
        children = new EventExecutor[nThreads];

        /*省略与本文内容无关的代码*/

        //创建一个执行器选择器
        chooser = chooserFactory.newChooser(children);
        
        /*省略与本文内容无关的代码*/
    }

在这个构造方法中,首先会根据线程数量来创建一个执行器数组,然后使用选择器工厂chooserFactory的newChooser方法来创建选择器。

然后,继续跟入DefaultEventExecutorChooserFactory的newChooser方法:

    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        //如果执行器的数量是2的倍数
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            //普通选择器
            return new GenericEventExecutorChooser(executors);
        }
    }

这里,首先调用isPowerOfTwo判断执行器数量是不是2的幂,

    //判断是否是2的次幂
    private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }

(val & -val) == val是一种快速判断val是不是2的幂的方法, -val 表示n的二进制补码的相反数。(val & -val)的结果是n的二进制表示中最右边的 1所对应的位。这意味着只有当val是2的幂时,(val & -val)的结果才等于val。

如果执行器的数量是2的幂,则使用PowerOfTwoEventExecutorChooser作为选择器,

    //如果执行器的数量是2的倍数
    private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
        //从0开始的计数器
        private final AtomicInteger idx = new AtomicInteger();
        //执行器数组
        private final EventExecutor[] executors;
        //构造方法
        PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }
        //选择其中一个执行器
        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    }

在PowerOfTwoEventExecutorChooser中,会定义一个从0开始的计数器,每次执行next()选择执行器以后,会将计数器的值增加1,根据计数器的值idx使用“ idx & executors.length - 1”方法来快速取模。
在Java语言中,算术运算符的优先级比位运算符高,因此“idx & executors.length - 1”等同于
“idx & (executors.length - 1)”。
当idx 为2的幂时,“idx & (executors.length - 1)”结果等于“idx % (executors.length - 1)”。

这种快速取模法和HashMap源码中的计算节点在哈希表位置时使用的快速取模法一模一样,只不过HashMap用的是哈希值和数组长度减去1后进行位运算。
我觉得,HashMap中哈希表的扩容之所以要以2倍来扩容,有一部分原因就是为了保证哈希表的长度始终是2的幂,这样就节点插入时就能使用这种快速取模法。

如果执行器的数量不是2的幂,则会使用GenericEventExecutorChooser作为选择器。

    private static final class GenericEventExecutorChooser implements EventExecutorChooser {
        //创建一个从0 开始的计数器
        private final AtomicLong idx = new AtomicLong();
        //执行器数组
        private final EventExecutor[] executors;

        GenericEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }
        @Override
        public EventExecutor next() {
            return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
        }
    }

逻辑和上面的基本一样,只不过取模方式是用“%”算术运算符来进行取模。

总结与思考

Netty中的执行器选择策略很简单,使用一个每次递增的计数器,然后每次执行next()选取执行器时,将计数器的值和执行器的数量进行取模。根据执行器的数量是不是2的幂,决定使用快速取模法和普通取模法。

不过笔者对这个DefaultEventExecutorChooserFactory代码有点疑问,在PowerOfTwoEventExecutorChooser 选择器中,其计数器使用的是AtomicInteger类型;在GenericEventExecutorChooser选择器中,其计数器使用的是AtomicLong型。为什么要不一样呢?

相关文章:

  • 实践深度学习:构建一个简单的图像分类器
  • K8s:kubernetes.io~csi 目录介绍
  • 【2025最新计算机毕业设计】基于SpringBoot+Vue非遗传承与保护研究系统【提供源码+答辩PPT+文档+项目部署】
  • 版本控制与Git实战指南:从入门到WebStorm集成
  • 暴雨受邀参加“DeepSeek影响与启示”特别论坛
  • buuctf刷题记录
  • win10系统上的虚拟机安装麒麟V10系统提示找不到操作系统
  • React 前端框架介绍
  • 国内加大政策推动,多层级标准建立产业规范
  • 外盘期货数据分析新视角:分钟级高频数据解析
  • Onedrive精神分裂怎么办(有变更却不同步)
  • 什么是知识文库?知识存储与管理的利器
  • 【和春笋一起学C++】if else语句
  • Linux运维篇-存储基础知识
  • Maven的依赖管理
  • 【Java】泛型与集合篇(一)
  • 《Python实战进阶》专栏 No2: Flask 中间件与请求钩子的应用
  • Pytorch论文实现之结合对抗、均方、感知三种损失以及对称卷积神经网络来实现超分辨率重建
  • BS5852英国家具防火安全条款主要包括哪几个方面呢?
  • deepseek-v3在阿里云和腾讯云的使用中的差异
  • 美国得克萨斯州发生5.4级地震,震源深度10千米
  • 中国海警局回应日本民用飞机侵闯我钓鱼岛领空:依法警告驱离
  • 长三角铁路今日预计发送390万人次,昨日客发量同比增长10.5%
  • 消费持续升温,这个“五一”假期有何新亮点?
  • 高速变道致连环车祸,白车“骑”隔离栏压住另一车,交警回应
  • 生命与大海相连:他在300多米的深海行走,在沉船一线打捞救援