NioEventLoop的创建

NioEventLoop是netty及其重要的组成部件,它的首要职责就是为注册在它上的channels服务,发现这些channels上发生的新连接、读写等I/O事件,然后将事件转交 channel 流水线处理。使用netty时,我们首先要做的就是创建NioEventLoopGroup,这是一组NioEventLoop的集合,类似线程池与线程池组。通常,服务端会创建2个group,一个叫做bossGroup,一个叫做workerGroup。bossGroup负责监听绑定的端口,发现端口上的新连接,初始化后交由workerGroup处理后续的读写事件。

  1. EventLoopGroup bossGroup = new NioEventLoopGroup(1);
  2. EventLoopGroup workerGroup = new NioEventLoopGroup();

我们先看看bossGroup和workerGroup的构造方法。

  1. public NioEventLoopGroup() {
  2. this(0);
  3. }
  4. public NioEventLoopGroup(int nThreads) {
  5. this(nThreads, (Executor) null);
  6. }
  7. 除此之外,还有多达8种构造方法,这些构造方法可以指定5种参数:
  8. 1、最大线程数量。如果指定为0,那么Netty会将线程数量设置为CPU逻辑处理器数量的2
  9. 2、线程工厂。要求线程工厂类必须实现java.util.concurrent.ThreadFactory接口。如果没有指定线程工厂,那么默认DefaultThreadFactory
  10. 3SelectorProvider。如果没有指定SelectorProvider,那么默认的SelectorProviderSelectorProvider.provider()。
  11. 4SelectStrategyFactory。如果没有指定则默认为DefaultSelectStrategyFactory.INSTANCE
  12. 5RejectedExecutionHandler。拒绝策略处理类,如果这个EventLoopGroup已被关闭,那么之后提交的Runnable任务会默认调用RejectedExecutionHandlerreject方法进行处理。如果没有指定,则默认调用拒绝策略。

最终,NioEventLoopGroup会重载到父类mUltiThreadEventExecutorGroup的构造方法上,这里省略了一些健壮性代码。

  1. protected MultithreadEventExecutorGroup(int nThreads, Executor executor,EventExecutorChooserFactory chooserFactory, Object... args) {
  2. // 步骤1
  3. if (executor == null) {
  4. executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
  5. }
  6. // 步骤2
  7. children = new EventExecutor[nThreads];
  8. for (int i = 0; i < nThreads; i ++) {
  9. children[i] = newChild(executor, args);
  10. }
  11. // 步骤3
  12. chooser = chooserFactory.newChooser(children);
  13. // 步骤4
  14. final FutureListener<Object> terminationListener = future -> {
  15. if (terminatedChildren.incrementAndGet() == children.length) {
  16. terminationFuture.setSuccess(null);
  17. }
  18. };
  19. for (EventExecutor e: children) {
  20. e.terminationFuture().addListener(terminationListener);
  21. }
  22. // 步骤5
  23. Set<EventExecutor> childrenSet = new LinkedHashSet<>(children.length);
  24. Collections.addAll(childrenSet, children);
  25. readonlyChildren = Collections.unmodifiableSet(childrenSet);
  26. }

步骤1

第一个步骤是创建线程池executor。从workerGroup构造方法可知,默认传进来的executor为null,所以首先创建executor。newDefaultThreadFactory的作用是设置线程的前缀名和线程优先级,默认情况下,前缀名是nioEventLoopGroup-x-y这样的命名规则,而线程优先级则是5,处于中间位置。
创建完newDefaultThreadFactory后,进入到ThreadPerTaskExecutor。它直接实现了juc包的线程池顶级接口,从构造方法可以看到它只是简单的把factory赋值给自己的成员变量。而它实现的接口方法调用了threadFactory的newThread方法。从名字可以看出,它构造了一个thread,并立即启动thread。

  1. public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
  2. this.threadFactory = threadFactory;
  3. }
  4. @Override
  5. public void execute(Runnable command) {
  6. threadFactory.newThread(command).start();
  7. }

那么我们回过头来看下DefaultThreadFactory的newThread方法,发现他创建了一个FastThreadLocalThread。这是netty自定义的一个线程类,顾名思义,netty认为它的性能更快。关于它的解析留待以后。这里步骤1创建线程池就完成了。总的来说他与我们通常使用的线程池不太一样,不设置线程池的线程数和任务队列,而是来一个任务启动一个线程。(问题:那任务一多岂不是直接线程爆炸?)

  1. @Override
  2. public Thread newThread(Runnable r) {
  3. Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
  4. return t;
  5. }
  6. protected Thread newThread(Runnable r, String name) {
  7. return new FastThreadLocalThread(threadGroup, r, name);
  8. }

步骤2

步骤2是创建workerGroup中的NioEventLoop。在示例代码中,传进来的线程数是0,显然不可能真正只创建0个nioEventLoop线程。在调用父类MultithreadEventLoopGroup构造函数时,对线程数进行了判断,若为0,则传入默认线程数,该值默认为2倍CPU核心数

  1. protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
  2. super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
  3. }
  4. // 静态代码块初始化DEFAULT+EVENT_LOOP_THREADS
  5. static {
  6. DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
  7. }

接下来是通过newChild方法为每一个EventExecutor创建一个对应的NioEventLoop。这个方法传入了一些args到NioEventLoop中,它们分别是:

  1. SlectorProvider.provider, 用于创建 Java NIO Selector 对象,本地是windows
  2. DefaultSelectStrategyFactory.INSTANCE, 选择策略工厂,默认有任务就执行selectNow()
  3. RejectedExecutionHandlers.reject(), 拒绝执行处理器,默认拒绝
  4. EventLoopTaskQueueFactory;任务队列工厂,默认为null

进入NioEventLoop的构造函数,如下:

  1. NioEventLoop构造函数
  2. NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
  3. SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
  4. EventLoopTaskQueueFactory queueFactory) {
  5. super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
  6. rejectedExecutionHandler);
  7. if (selectorProvider == null) {
  8. throw new NullPointerException("selectorProvider");
  9. }
  10. if (strategy == null) {
  11. throw new NullPointerException("selectStrategy");
  12. }
  13. provider = selectorProvider;
  14. final SelectorTuple selectorTuple = openSelector();
  15. selector = selectorTuple.selector;
  16. unwrappedSelector = selectorTuple.unwrappedSelector;
  17. selectStrategy = strategy;
  18. }
  19. // 父类构造函数
  20. protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
  21. boolean addTaskWakesUp, Queue<Runnable> taskQueue,
  22. RejectedExecutionHandler rejectedHandler) {
  23. super(parent);
  24. this.addTaskWakesUp = addTaskWakesUp;
  25. this.maxPendingTasks = DEFAULT_MAX_PENDING_EXECUTOR_TASKS;
  26. this.executor = ThreadExecutorMap.apply(executor, this);
  27. this.taskQueue = ObjectUtil.checkNotNull(taskQueue, taskQueue");
  28. rejectedExecutionHandler = ObjectUtil.checkNotNullrejectedHandler, "rejectedHandler");
  29. }

首先调用一个newTaskQueue方法创建一个任务队列。这是一个mpsc即多生产者单消费者的无锁队列。之后调用父类的构造函数,在父类的构造函数中,将NioEventLoopGroup设置为自己的parent,并通过匿名内部类创建了这样一个Executor————通过ThreadPerTaskExecutor执行传进来的任务,并且在执行时将当前线程与NioEventLoop绑定。其他属性也一一设置。
在nioEventLoop构造函数中,我们发现创建了一个selector,不妨看一看netty对它的包装。

  1. unwrappedSelector = provider.openSelector();
  2. if (DISABLE_KEY_SET_OPTIMIZATION) {
  3. return new SelectorTuple(unwrappedSelector);
  4. }

首先看到netty定义了一个常量DISABLE_KEY_SET_OPTIMIZATION,如果这个常量设置为true,也即不对keyset进行优化,则直接返回未包装的selector。那么netty对selector进行了哪些优化?

  1. final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
  2. final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
  3. SelectionKey[] keys;
  4. int size;
  5. SelectedSelectionKeySet() {
  6. keys = new SelectionKey[1024];
  7. }
  8. }

往下,我们看到了一个叫做selectedSelectionKeySet的类,点进去可以看到,它继承了AbstractSet,然而它的成员变量却让我们想到了ArrayList,再看看它定义的方法,除了不支持remove和contains外,活脱脱一个简化版的ArrayList,甚至也支持扩容。
没错,netty确实通过反射的方式,将selectionKey从Set替换为了ArrayList。仔细一想,却又觉得此番做法有些道理。众所周知,虽然HashSet和ArrayList随机查找的时间复杂度都是o(1),但相比数组直接通过偏移量定位,HashSet由于需要Hash运算,时间消耗上又稍稍逊色了些。再加上使用场景上,都是获取selectionKey集合然后遍历,Set去重的特性完全用不上,也无怪乎追求性能的netty想要替换它了。

步骤3

创建完workerGroup的NioEventLoop后,如何挑选一个nioEventLoop进行工作是netty接下来想要做的事。一般来说轮询是一个很容易想到的方案,为此需要创建一个类似负载均衡作用的线程选择器。当然追求性能到丧心病狂的netty是不会轻易满足的。我们看看netty在这样常见的方案里又做了哪些操作。

  1. public EventExecutorChooser newChooser(EventExecutor[] executors) {
  2. if (isPowerOfTwo(executors.length)) {
  3. return new PowerOfTwoEventExecutorChooser(executors);
  4. } else {
  5. return new GenericEventExecutorChooser(executors);
  6. }
  7. }
  8. // PowerOfTwo
  9. public EventExecutor next() {
  10. return executors[idx.getAndIncrement() & executors.length - 1];
  11. }
  12. // Generic
  13. public EventExecutor next() {
  14. return executors[Math.abs(idx.getAndIncrement() % executors.length)];
  15. }

可以看到,netty根据workerGroup内线程的数量采取了2种不同的线程选择器,当线程数x是2的幂次方时,可以通过&(x-1)来达到对x取模的效果,其他情况则需要直接取模。这与hashmap强制设置容量为2的幂次方有异曲同工之妙。

步骤4

步骤4就是添加一些保证健壮性而添加的监听器了,这些监听器会在EventLoop被关闭后得到通知。

步骤5

创建一个只读的NioEventLoop线程组

到此NioEventLoopGroup及其包含的NioEventLoop组就创建完成了

NioEventLoop的创建的更多相关文章

  1. Netty源码分析之NioEventLoop(一)—NioEventLoop的创建

    一.NioEventLoop的概述 NioEventLoop做为Netty线程模型的核心部分,从本质上讲是一个事件循环执行器,每个NioEventLoop都会绑定一个对应的线程通过一个for(;;)循 ...

  2. Netty源码分析第2章(NioEventLoop)---->第2节: NioEventLoopGroup之NioEventLoop的创建

    Netty源码分析第二章: NioEventLoop   第二节: NioEventLoopGroup之NioEventLoop的创建 回到上一小节的MultithreadEventExecutorG ...

  3. 2.NioEventLoop的创建

    NioEventLoop的创建 NioEventLoop是netty及其重要的组成部件,它的首要职责就是为注册在它上的channels服务,发现这些channels上发生的新连接.读写等I/O事件,然 ...

  4. Netty源码分析第2章(NioEventLoop)---->第1节: NioEventLoopGroup之创建线程执行器

    Netty源码分析第二章: NioEventLoop 概述: 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个 ...

  5. Netty 源码 NioEventLoop(一)初始化

    Netty 源码 NioEventLoop(一)初始化 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) Netty 基于事件 ...

  6. Netty源码分析第2章(NioEventLoop)---->第3节: 初始化线程选择器

    Netty源码分析第二章:NioEventLoop   第三节:初始化线程选择器 回到上一小节的MultithreadEventExecutorGroup类的构造方法: protected Multi ...

  7. Netty源码分析第2章(NioEventLoop)---->第4节: NioEventLoop线程的启动

    Netty源码分析第二章: NioEventLoop   第四节: NioEventLoop线程的启动 之前的小节我们学习了NioEventLoop的创建以及线程分配器的初始化, 那么NioEvent ...

  8. Netty源码分析第2章(NioEventLoop)---->第8节: 执行任务队列

      Netty源码分析第二章: NioEventLoop   第八节: 执行任务队列 继续回到NioEventLoop的run()方法: protected void run() { for (;;) ...

  9. Netty源码分析之NioEventLoop(三)—NioEventLoop的执行

    前面两篇文章Netty源码分析之NioEventLoop(一)—NioEventLoop的创建与Netty源码分析之NioEventLoop(二)—NioEventLoop的启动中我们对NioEven ...

随机推荐

  1. 重大升级!SEER见证人,您的节点需要在10月28日前更新

    SEER的区块链底层目前还处于不断完善中.一些新的完善更新会为区块链的基础设施--节点软件添加新的功能.理事会将会就是否接受新的节点版本进行共识投票,如果提案投票通过,将要求所有见证人在指定时间前将节 ...

  2. Luogu1119灾后重建

    题目背景 BBB 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响.但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车.换句话说,只有连接着两个重建完成的村庄的公 ...

  3. CF991D Bishwock

    CF991D Bishwock 题目描述 给一个$2\times n$的网格,上面一些位置以及被覆盖上了.现在你有一种形状为L的小块,每个由三个小格组成,构成L型 现在问你,当前的网格最多还能摆多少小 ...

  4. vue 自定义侧边栏 递归无限子级菜单

    有很多网站会涉及到导航栏,我自己在开发中用的是element导航组件,并且自定义事件,给大家分享一下. 1.使用递归方法,无限循环子级菜单. 2.使用组件封装,维护方便. 3.使用index作为路由跳 ...

  5. 百万年薪python之路 -- MySQL数据库之 永久修改字符串编码 与 忘了密码和修改密码

    永久修改字符集编码的方法: 在mysql安装目录下创建一个my.ini(Windows下)文件,写入下面的配置,然后重启服务端. [client] #设置mysql客户端默认字符集 default-c ...

  6. TextBox各种设置

    前台: <StackPanel> <TextBlock Margin=" TextWrapping="Wrap"> TextBlock with ...

  7. 了解这一行的,腰包都鼓鼓的了,程序辅导,CS作业

    我们都知道计算机这类理科专业,可能你打开电脑聊几分钟微信,可能你就已经错过了成为程序员大佬的机会.就像数学专业的同学弯腰捡了一支笔,然后发现黑板被写满从此再也没有学懂数学.所以课后的作业自然也就没法顺 ...

  8. day27作业

    FTP需求 客户端可以注册登录 client:输入一个login sever:执行login 客户端登陆后可以选择文件夹上传与下载 client:输入一个upload,download sever:执 ...

  9. class定义类 及 实现继承

    class 定义类 代码如下: class Student { constructor(name) { this.name = name; } sayHello() { console.log(&qu ...

  10. redis 基本类型和命令(一)

    一.Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). (1) string类型是Redis最基本的数 ...