NioEventLoopGroup 对象的创建

先贴出一张 NioEventLoopGroupNioEventLoop 继承类图。

前言

示例代码

// BossGroup 仅需 1 线程处理连接请求
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// WorkerGroup 按 CPU 核心数动态分配(默认核心数的 2 倍)
EventLoopGroup workerGroup = new NioEventLoopGroup();

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class)
         .childHandler(new MyChannelInitializer());

上面的代码使用 Netty 的主从 Reactor 架构。该架构由主 Reactor 和从 Reactor 组成:

  • BossGroup(主 Reactor):负责监听 TCP 连接请求(accept 事件)。通常配置为单线程(new NioEventLoopGroup(1))。
  • WorkerGroup(从 Reactor):监听并处理已建立连接的 I/O 读写事件(read/write 事件)。Netty 默认将 WorkerGroup 中的线程数其设置为 CPU 核心数的 2 倍。

NioEventLoopGroup 是 Netty 中基于 Java NIO 实现的事件循环线程池,属于 Reactor 线程模型的核心组件。它可以看做一个线程池,管理一组 NioEventLoop 线程。每个 NioEventLoop 绑定一个独立线程和 Selector,以串行无锁化方式处理网络事件,确保线程安全。

我们可以将 NioEventLoop 看成是 NioEventLoopGroup 的一个特殊情况。即只管理一个 NioEventLoopEventLoopGroup 。这也解释了为什么 EventLoop 接口继承了 EventLoopGroup 接口的原因。

从构造函数开始

NioEventLoopGroup 有多达 12 个构造函数。我们主要关注下面两个:

  • NioEventLoopGroup():无参构造。
  • NioEventLoopGroup(int nThreads):单形参构造函数。nThreads 指定了线程池的线程数量。
public NioEventLoopGroup() {
    this(0);
}

public NioEventLoopGroup(int nThreads) {
    this(nThreads, (Executor) null);
}

无参构造函数以参数 nThreads = 0 调用单形参构造函数。

public NioEventLoopGroup(int nThreads, Executor executor) {
    // Linux 中使用 EPollSelectorProvider
    this(nThreads, executor, SelectorProvider.provider());
}

SelectorProvider.provider() 在不同系统中返回的对象也不相同。在 Linux 系统中返回 EPollSelectorProvider 对象。

public NioEventLoopGroup(
    int nThreads, Executor executor, final SelectorProvider selectorProvider) {
    this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}

新增形参 DefaultSelectStrategyFactory

public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                         final SelectStrategyFactory selectStrategyFactory) {
    super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}

新增形参 RejectedExecutionHandler

NioEventLoopGroup 的父类是 MultithreadEventLoopGroupsuper() 进入 MultithreadEventLoopGroup 的构造函数。

protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

前面可知,当我们调用的是无参构造函数时,nThreads 会默认设置为 0。当然如果你调用单行参构造函数手动指定 nThreads 为 0 是相同的效果。

这里判断,当你的线程数量 nThreads 为 0 的时候,会使用DEFAULT_EVENT_LOOP_THREADS当作线程池的线程数量。
DEFAULT_EVENT_LOOP_THREADS 是多少呢?

DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

如果没有使用 -Dio.netty.eventLoopThreads 指定线程数,则默认是 CPU 核心数的两倍。

结论:当我们创建 NioEventLoopGroup 时,不指定线程数量,系统会默认使用系统 CPU 核数 * 2 当作线程的数量。

我们上一步传递过来的 selectorProvider、拒绝策略、selectStrategyFactory 被封装为参数数组 args,并分别放在 args[0]args[1]args[2] 的位置!

调用父类 MultithreadEventExecutorGroup 构造函数:

protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
    this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}

新增形参 DefaultEventExecutorChooserFactory

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                        EventExecutorChooserFactory chooserFactory, Object... args) {
    checkPositive(nThreads, "nThreads");

    if (executor == null) {
        executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }

    children = new EventExecutor[nThreads];

    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {
            children[i] = newChild(executor, args);
            success = true;
        } catch (Exception e) {
            // TODO: Think about if this is a good exception type
            throw new IllegalStateException("failed to create a child event loop", e);
        } finally {
            if (!success) {
                for (int j = 0; j < i; j ++) {
                    children[j].shutdownGracefully();
                }

                for (int j = 0; j < i; j ++) {
                    EventExecutor e = children[j];
                    try {
                        while (!e.isTerminated()) {
                            e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                        }
                    } catch (InterruptedException interrupted) {
                        // Let the caller handle the interruption.
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }
    }

    chooser = chooserFactory.newChooser(children);

    final FutureListener<Object> terminationListener = new FutureListener<Object>() {
        @Override
        public void operationComplete(Future<Object> future) throws Exception {
            if (terminatedChildren.incrementAndGet() == children.length) {
                terminationFuture.setSuccess(null);
            }
        }
    };

    for (EventExecutor e: children) {
        e.terminationFuture().addListener(terminationListener);
    }

    Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
    Collections.addAll(childrenSet, children);
    readonlyChildren = Collections.unmodifiableSet(childrenSet);
}

上面这个 MultithreadEventExecutorGroup 构造函数就是创建 NioEventLoopGroup 对象的主要逻辑。下面我们分多个部分讲解。

核心逻辑

创建 Executor

if (executor == null) {
    executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}

如果没有指定 ExecutorEventLoopGroup 会默认创建一个 ThreadPerTaskExecutor 对象。正如 ThreadPerTaskExecutor 的名称所示,每次都会创建一个新的线程去执行任务。

创建 ThreadPerTaskExecutor 对象需要传入一个线程工厂对象 ThreadFactory

newDefaultThreadFactory()
protected ThreadFactory newDefaultThreadFactory() {
    return new DefaultThreadFactory(getClass());
}

getClass() 返回的是 EventLoopGroup 类的 Class 对象。

ThreadPerTaskExecutor

ThreadPerTaskExecutor 类非常简单。

public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
    }

    @Override
    public void execute(Runnable command) {
        threadFactory.newThread(command).start();
    }
}

当我们调用 executeThreadPerTaskExecutor 提交一个任务时,会通过线程工厂(DefaultThreadFactory)创建了一个新的线程,start 方法启动线程执行任务。

DefaultThreadFactory.newThread()
@Override
public Thread newThread(Runnable r) {
//创建一个线程 每次执行任务的时候都会创建一个线程实体
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
try {
    if (t.isDaemon() != daemon) {
        t.setDaemon(daemon);
    }

    if (t.getPriority() != priority) {
        t.setPriority(priority);
    }
} catch (Exception ignored) {
    // Doesn't matter even if failed to set.
}
return t;
}

使用 FastThreadLocalRunnable 包装原生 Runnable

我们继续跟进到 newThread 方法:

protected Thread newThread(Runnable r, String name) {
    return new FastThreadLocalThread(threadGroup, r, name);
}

FastThreadLocalThread 是 Nnetty 自定义的线程类,继承自 java.lang.Thread

创建 EventExecutor

我们知道 NioEventLoopGroup 管理一组 NioEventLoop。因此需要一个容器去存放这些 NioEventLoop

NioEventLoopGroupchildren 字段就是那个容器,它是一个数组类型。之所使用数组类型,就是为了能进行高效的访问。

数组长度由 nThreads 指定。下面 for 循环创建 NioEventLoop,并放入 children 数组中进行统一管理。

children = new EventExecutor[nThreads]; // 使用数组存放EventExecutors

for (int i = 0; i < nThreads; i ++) {
    boolean success = false;
    try {
        children[i] = newChild(executor, args); // 核心,子类实现
        success = true;
    } catch (Exception e) {
        // TODO: Think about if this is a good exception type
        throw new IllegalStateException("failed to create a child event loop", e);
    } finally {
        // 中途创建失败,关闭之前创建成功的EventExecutor
        if (!success) {
            for (int j = 0; j < i; j ++) {
                children[j].shutdownGracefully();
            }

            for (int j = 0; j < i; j ++) {
                EventExecutor e = children[j];
                try {
                    while (!e.isTerminated()) {
                        e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                    }
                } catch (InterruptedException interrupted) {
                    // Let the caller handle the interruption.
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}

MultithreadEventExecutorGroup##newChild 由子类实现,因此我们进入 NioEventLoopGroup##newChild 方法:

@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
    return new NioEventLoop(this, executor, (SelectorProvider) args[0],
                            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}

传递过来的 args 参数数组长度为 3,分别为:

  • args[0]:selectorProvider
  • args[1]:RejectedExecutionHandler
  • args[2]:SelectStrategyFactory

因此 arg[3] = queueFactory 为 null, 然后我们再重点关注 NioEventLoop 对象,可以看出,newChild 方法返回的是 NioEventLoop 对象。那么印证了我们之前的结论,NioEventLoopGroup 创建并管理一组 NioEventLoop 对象!

创建 EventExecutorChooser

chooser = chooserFactory.newChooser(children);

默认 chooserFactoryDefaultEventExecutorChooserFactory 类型。

我们进入到 DefaultEventExecutorChooserFactory##newChooser 中,并将上一步填充好的数组作为参数:

@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
    if (isPowerOfTwo(executors.length)) {
        return new PowerOfTwoEventExecutorChooser(executors);
    } else {
        return new GenericEventExecutorChooser(executors);
    }
}

NioEventLoopGroup 管理一组 NioEventLoopEventExecutorChooser 就是 NioEventLoopGroup 用来获取它管理的 NioEventLoop 的。

这里根据数组的长度(即 NioEventLoop 的数量)是否是 2 的幂次方,进行不同的处理:

  • 是 2 的幂次方:创建 PowerOfTwoEventExecutorChooser
  • 不是2 的幂次方:创建 GenericEventExecutorChooser

PowerOfTwoEventExecutorChooser

PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
    this.executors = executors;
}

@Override
public EventExecutor next() {
    return executors[idx.getAndIncrement() & executors.length - 1];
}

GenericEventExecutorChooser

GenericEventExecutorChooser(EventExecutor[] executors) {
    this.executors = executors;
}

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

PowerOfTwoEventExecutorChooserGenericEventExecutorChooser 的功能相同。都是循环从数组中获取 NioEventLoop,当到达数组末尾,重新回到数组头部重新获取。通过模运算让数组达到环形数组的效果。

它们的区别是:如果数组长度是 2 的幂次方,则可以使用位运算 & 实现模运算;否则,只能使用 % 来实现模运算。

我们知道,CPU 最擅长处理的就是位运算。因此这算是 Netty 做的一点优化。

注册关闭监听器

final FutureListener<Object> terminationListener = new FutureListener<Object>() {
    @Override
    public void operationComplete(Future<Object> future) throws Exception {
        if (terminatedChildren.incrementAndGet() == children.length) {
            terminationFuture.setSuccess(null);
        }
    }
};

for (EventExecutor e: children) {
    e.terminationFuture().addListener(terminationListener);
}

上面代码逻辑分为两部分:

  • 创建关闭监听器 terminationListener
  • 将监听器注册到每一个 NioEventLoop 中。

只读 Set 容器

Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);

上面知道,我们为了性能使用数组存放 NioEventLoop,但是数组不方便迭代遍历,且容易遭到篡改。因此我们再创建一个只读的 Set 容器 readonlyChildren 存放 NioEventLoop,用来进行迭代遍历。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值