Netty线程池原理

在查看源码前,先了解下Netty中的线程池EventLoopGroup是如何执行任务的,因为源码中很多异步操作都是把任务提交到EventLoopGroup中。

EventLoopGroup

EventLoopGroup 可以理解为一个线程池,以NioEventLoopGroup为例查看继承体系。

Netty线程池原理_第1张图片

  • Iterable,可以通过迭代的方式查看里面的元素。
  • Executor,线程池的顶级接口,包含一个execute()方法,用于提交任务到线程池中。
  • ExecutorService,继承了Executor接口,并新增了一些方法,如submit()方法、shutdown()方法。
  • ScheduledExecutorService,继承了ExecutorService接口,增加了定时任务相关的方法。

上面四个接口,后面三个类都是线程池相关的接口。

  • EventExecutorGroup,继承了ScheduledExecutorService,提供了next()方法用于获取一个EventExecutor
  • EventLoopGroup,扩展自 EventExecutorGroup,并增加或修改了两大功能,一是提供了 next() 方法用于获取一个 EventLoop,二是提供了注册 Channel 到事件轮询器中。
  • MultithreadEventLoopGroup,抽象类,EventLoopGroup 的所有实现类都继承自这个类,可以看作是一种模板,从名字也可以看出来它里面包含多个线程来处理任务。
  • NioEventLoopGroup,具体实现类,使用 NIO 形式(多路复用中的 select)工作的 EventLoopGroup。更换前缀就可以得到不同的实现类,比如 EpollEventLoopGroup 专门用于 Linux 平台,KQueueEventLoopGroup 专门用于 MacOS/BSD 平台。

EventLoop

EventLoop 可以理解为是 EventLoopGroup 中的工作线程,类似于Java线程池中的工作线程,它里面包含了一个线程,控制着这个线程的生命周期。

NioEventLoop 为例看看它的继承体系:

Netty线程池原理_第2张图片

  • EventExecutor,扩展自 EventLoopGroup,主要增加了判断一个线程是不是在 EventLoop 中的方法。

  • OrderedEventExecutor,扩展自 EventExecutor,这是一个标记接口,标志着里面的任务都是按顺序执行的。

  • EventLoop,扩展自 EventLoopGroup,它将为已注册进来的 Channel 处理所有的 IO 事件,另外,它还扩展自 OrderedEventExecutor 接口,说明里面的任务是按顺序执行的。

  • SingleThreadEventLoop,抽象类,EventLoop 的所有实现类都继承自这个类,可以看作是一种模板,从名字也可以看出来它是使用单线程处理的。

  • NioEventLoop,具体实现类,绑定到一个 Selector 上,同时可以注册多个 ChannelSelector 上,同时,它继承自 SingleThreadEventLoop,也就说明了一个 Selector 对应一个线程。同样地,更换前缀就可以得到不同的实现,比如 EpollEventLoopKQueueEventLoop

    Netty线程池原理_第3张图片

NioEventLoop执行原理

NioEventLoopGroup为例,通过源码分析EventLoopGroup是如何执行任务的。

public class Main {
    public static void main(String[] args) throws IOException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(4);
      	// 这里的next()方法返回的是NioEventLoop实例
        // execute()方法提交任务
        bossGroup.next().execute(() -> {
            System.out.println(Thread.currentThread() + ":" + LocalDateTime.now().toString());
        });
        System.in.read();
    }
}

查看NioEventLoopGroup的构造方法,最后会进入到父类MultithreadEventExecutorGroup的构造方法。


public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
		// 这里的具体实现就是 NioEventLoop
    private final EventExecutor[] children;
    // EventExecutor 选择器, 通过next()方法从 children 列表里面选择一个 EventExecutor 执行任务 
    private final EventExecutorChooserFactory.EventExecutorChooser chooser;
		// 构造方法
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        // 线程数量必须大于0
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        }
        // 这里的executor是用来创建线程的
        if (executor == null) {
            // newDefaultThreadFactory()返回的是io.netty.util.concurrent.DefaultThreadFactory,继承自 java.util.concurrent.ThreadFactory,
            // NioEventLoop 里面的线程(FastThreadLocalThread)就是通过这个生成的
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); // ①
        }
        // 创建数组
        children = new EventExecutor[nThreads];
        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                // newChild 方法由子类 NioEventLoopGroup 实现
                // 返回一个 NioEventLoop
                children[i] = newChild(executor, args); // ②
            } 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 {
                // 省略......
            }
        }
        /**
        * 线程选择器
        * 如果线程数量是2的N次方, 则使用:PowerOfTwoEventExecutorChooser, 否则使用GenericEventExecutorChooser
        * chooser是用来从children里面选择EventExecutor(这里的具体实现是NioEventLoop), 前者做了优化, 更加高效
        * 前者: executors[idx.getAndIncrement() & executors.length - 1]
        * 后者: executors[(int) Math.abs(idx.getAndIncrement() % executors.length)]
        */
        chooser = chooserFactory.newChooser(children);
        // 省略......
    }
}

ThreadPerTaskExecutor是用来创建线程的,通过execute方法创建线程并执行任务,这里面创建的线程是Netty实现的FastThreadLocalThread

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();
    }
}

②创建NioEventLoop

public class NioEventLoopGroup extends MultithreadEventLoopGroup {
    @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);
    }
}

查看上面创建NioEventLoop实例的构造方法,里面主要有下面几个重要参数。

public final class NioEventLoop extends SingleThreadEventLoop {
    // Java NIO 中的 java.nio.channels.Selector
  	// 用来绑定Socket事件
    private Selector selector;
    private Selector unwrappedSelector;
  
    /**
    * 父类 SingleThreadEventExecutor 中
    * 有个任务队列, 用来存放提交的任务
    * private final Queue taskQueue;
    *
    * 具体实现是 ThreadPerTaskExecutor, 用来创建线程并执行任务
    * private final Executor executor;
    *
    * 通过 ThreadPerTaskExecutor 创建出来的 FastThreadLocalThread
    * private volatile Thread thread;
    */
}

NioEventLoop是如何执行任务的?在这里打个断点,跟踪源码进去查看

bossGroup.next().execute(() -> {
    System.out.println(Thread.currentThread() + ":" + LocalDateTime.now().toString());
});

NioEventLoop执行任务的入口在父类SingleThreadEventExecutorexecute方法

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
    // 任务队列
    private final Queue<Runnable> taskQueue;
    // 执行线程
    private volatile Thread thread;
    // 具体实现是 ThreadPerTaskExecutor
    private final Executor executor;

    // 1. NioEventLoop执行任务的入口
    @Override
    public void execute(Runnable task) {
        ObjectUtil.checkNotNull(task, "task");
        execute(task, !(task instanceof LazyRunnable) && wakesUpForTask(task));
    }

    private void execute(Runnable task, boolean immediate) {
        // 判断调用线程是否等于内部线程 this.thread == Thread.currentThread()
        boolean inEventLoop = inEventLoop();
        // 放入任务队列
        addTask(task);
        if (!inEventLoop) {
            // inEventLoop为false, 外部线程调用NioEventLoop
            // 启动线程
            startThread();
            // 省略......
        }
        // 省略......
    }

    private void startThread() {
        // 这里会判断, 只能启动一次
        if (state == ST_NOT_STARTED) {
            // CAS操作
            if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                boolean success = false;
                try {
                    // 开始创建线程
                    doStartThread();
                    success = true;
                } finally {
                    if (!success) {
                        STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                    }
                }
            }
        }
    }
  
    private void doStartThread() {
        assert thread == null;
        // 这里执行的是ThreadPerTaskExecutor.execute()方法, 会创建线程并执行传进去的Runnable任务
        executor.execute(new Runnable() {
            @Override
            public void run() {
                // Thread.currentThread()返回的是上面调用ThreadPerTaskExecutor.execute()方法生成的线程
                thread = Thread.currentThread();
                // 省略......
                try {
                    // 这里会执行NioEventLoop里面的run方法
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    // 省略......
                }
            }
        });
    }

}

run()

NioEventLooprun方法里面是一个死循环,里面负责监听Selector上的IO事件和执行提交进来的异步任务。

public final class NioEventLoop extends SingleThreadEventLoop {
    private Selector selector;
    private Selector unwrappedSelector;

    private final IntSupplier selectNowSupplier = new IntSupplier() {
        @Override
        public int get() throws Exception {
            return selectNow();
        }
    };
    int selectNow() throws IOException {
        return selector.selectNow();
    }

    @Override
    protected void run() {
        // 记录发生空转的次数, 既没有IO需要处理、也没有执行任何任务
        int selectCnt = 0;
        for (;;) {
            try {
                int strategy;
                try {
                    // hasTasks() 是判断任务队列是否有任务
                    // 如果 hasTasks()为true, 则返回selectNowSupplier.get(), 否则返回 SelectStrategy.SELECT
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    case SelectStrategy.CONTINUE: // -2
                        continue;
                    case SelectStrategy.BUSY_WAIT: // -3
                        // fall-through to SELECT since the busy-wait is not supported with NIO

                    case SelectStrategy.SELECT: // -1
                        // 获取下一个定时任务执行时间
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            // 再次判断有没有任务
                            if (!hasTasks()) {
                                // key 调用select
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            // This update is just to help block unnecessary selector wakeups
                            // so use of lazySet is ok (no race condition)
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                        // fall through
                    default:
                    }
                } catch (IOException e) {
                    // If we receive an IOException here its because the Selector is messed up. Let's rebuild
                    // the selector and retry. https://github.com/netty/netty/issues/8566
                    // 出现异常, 重新构建Selector并注册事件
                    rebuildSelector0();
                    selectCnt = 0;
                    handleLoopException(e);
                    continue;
                }
                // 计算器加1
                selectCnt++;
                cancelledKeys = 0;
                needsToSelectAgain = false;
                // 控制处理执行io事件时间的占用比例, 默认是百分之50
                // 一半时间用来处理io事件, 一半时间用来处理任务队列taskQueue里面的任务
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                if (ioRatio == 100) { 
                    try {
                        if (strategy > 0) {
                            // 处理IO事件
                            processSelectedKeys();
                        }
                    } finally {
                        // Ensure we always run tasks.
                        // key 执行taskQueue中全部的任务
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) { // 如果 ioRatio != 100, 优先处理IO事件
                    final long ioStartTime = System.nanoTime();
                    try {
                        // 处理IO事件
                        processSelectedKeys();
                    } finally {
                        // 计算处理IO花费的时间
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        // key ioTime * (100 - ioRatio) / ioRatio 是计算任务队列执行的时间
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    // 0 代表运行最小数量的任务, 即运行63个任务
                    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
                }
                // 执行了任务队列的任务或者是有IO事件
                if (ranTasks || strategy > 0) {
                    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                selectCnt - 1, selector);
                    }
                    selectCnt = 0;
                } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
                    selectCnt = 0;
                    // 如果(!ranTasks && strategy <= 0) 即任务队里面没有任务, 也没有IO事件
                    // 则会执行 unexpectedSelectorWakeup(selectCnt) 
                    // 如果 selectCnt 达到一定最大值(默认为512), 重新构建Selector并注册事件, 防止 JDK BUG空轮训
                    // 这个BUG好像是虽然调用了阻塞的selector.select()
                    // 但是由于操作系统底层发现socket断开,还是会返回0,然后又没能处理相应的事件
                  	// 而且任务队列也为空的情况下,就会死循环下去,造成CPU100%
                  	// Netty的解决方案就是用了一个变量selectCnt统计轮询的次数。
                }
            } catch (CancelledKeyException e) {
                // Harmless exception - log anyway
                if (logger.isDebugEnabled()) {
                    logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                            selector, e);
                }
            } catch (Error e) {
                throw (Error) e;
            } catch (Throwable t) {
                handleLoopException(t);
            } finally {
                // Always handle shutdown even if the loop processing threw an exception.
                try {
                    if (isShuttingDown()) {
                        closeAll();
                        if (confirmShutdown()) {
                            return;
                        }
                    }
                } catch (Error e) {
                    throw (Error) e;
                } catch (Throwable t) {
                    handleLoopException(t);
                }
            }
        }
    }
}

select(long deadlineNanos)

根据deadlineNanos的值选择是无限期阻塞还是阻塞一段时间直接返回

public final class NioEventLoop extends SingleThreadEventLoop {
    private int select(long deadlineNanos) throws IOException {
        if (deadlineNanos == NONE) {
            // 无限期阻塞
            return selector.select();
        }
        // Timeout will only be 0 if deadline is within 5 microsecs
        // 如果deadlineNanos小于5纳秒, 则为0, 否则取整为1毫秒
        // 这段操作是为了向上取整, 转成毫秒
        long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
        // 不阻塞的selectNow()和阻塞一段时间的select(timeout)
        return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
    }
}

runAllTasks(long timeoutNanos)

timeoutNanos为0时,最多只能执行63个任务。

timeoutNanos大于0时,如果已执行任务的数量加1是64的整数倍,执行时间超过timeoutNanos则先停下来返回。

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
    protected boolean runAllTasks(long timeoutNanos) {
        // 这里会将定时任务队列里面的可执行任务拿出来放到任务队列里
        fetchFromScheduledTaskQueue();
        // 从任务队列里面获取任务
        Runnable task = pollTask();
        if (task == null) {
            // 去执行tailTasks的任务, 暂时还不知道这个是什么情况添加的任务
            afterRunningAllTasks();
            return false;
        }
        // 截至时间, 已经花费调度任务的时间+超时时间 
        // ScheduledFutureTask.nanoTime() 表示从开始到现在执行任务持续的时间
        final long deadline = timeoutNanos > 0 ? ScheduledFutureTask.nanoTime() + timeoutNanos : 0;
        // 记录执行任务数量
        long runTasks = 0;
        long lastExecutionTime;
        for (;;) {
            // 执行任务
            safeExecute(task);
            // 执行任务数量加1
            runTasks ++;

            // Check timeout every 64 tasks because nanoTime() is relatively expensive.
            // XXX: Hard-coded value - will make it configurable if it is really a problem.
            if ((runTasks & 0x3F) == 0) {
                // 当执行任务到达一定量时计算下时间
                lastExecutionTime = ScheduledFutureTask.nanoTime();
                // 如果调度任务的时间超过截止时间了, 那就退出了, 否则时间太长了
                if (lastExecutionTime >= deadline) {
                    break;
                }
            }
            // 继续获取任务
            task = pollTask();
            if (task == null) {
                lastExecutionTime = ScheduledFutureTask.nanoTime();
                break;
            }
        }
        // 这里会执行tailTasks里面的任务
        afterRunningAllTasks();
        this.lastExecutionTime = lastExecutionTime;
        return true;
    }
}

runAllTasks()

将任务队列的任务全部执行完后再返回。

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
    protected boolean runAllTasks() {
        assert inEventLoop();
        boolean fetchedAll;
        boolean ranAtLeastOne = false;

        do {
            // 这里会将定时任务队列里面的可执行任务拿出来放到任务队列里
            fetchedAll = fetchFromScheduledTaskQueue();
            // 执行队列里面的所有任务
            if (runAllTasksFrom(taskQueue)) {
                ranAtLeastOne = true;
            }
        } while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.
        // while循环保证定时任务都能被执行完

        if (ranAtLeastOne) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
        }
        // 这里会执行tailTasks里面的任务
        afterRunningAllTasks();
        return ranAtLeastOne;
    }
}

总结

  1. 每一个NioEventLoop都有一个自己的java.nio.channels.Selector,可以往这个Selector上注册我们感兴趣的事件。
  2. 每一个NioEventLoop都有一个自己的任务队列,提交的任务先放入到这个队列。
  3. 每一个NioEventLoop都有一个线程Thread
  4. 任务队列只有一个线程消费任务。
  5. Epoll的空轮询BUG解决方案

重要组件说明

ChannelHandler

ChannelHandler是核心业务处理接口,用于处理或拦截IO事件,并将其转发到ChannelPipeline中的下一个ChannelHandler,运用的是责任链设计模式。

ChannelHandler分为入站和出站两种:ChannelInboundHandlerChannelOutboundHandler,一般不建议直接实现这两个接口,而是它们的抽象类:

  • SimpleChannelInboundHandler:处理入站事件,它可以帮我们做资源的自动释放等操作。不建议直接使用 ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter:处理出站事件。
  • ChannelDuplexHandler:既可以处理入站也可以处理出站。

ChannelHandlerContext

默认实现DefaultChannelHandlerContext,里面有ChannelHandlerChannelPipeline的引用。

ChannelPipeline中的节点就是一个个ChannelHandlerContext

ChannelPipeline

ChannelPipelineChannelHandler的集合,它负责处理和拦截入站和出站的事件和操作,每个Channel都有一个ChannelPipeline与之对应,会自动创建。

ChannelPipeline中存储的是ChannelHandlerContext链,通过这个链把ChannelHandler连接起来。

  • 一个Channel对应一个ChannelPipeline
  • 一个ChannelPipeline包含一条双向的ChannelHandlerContext
  • 一个ChannelHandlerContext中包含一个ChannelHandler
  • 一个Channel会绑定到一个EventLoop
  • 一个NioEventLoop维护了一个Selector(使用的是 Java 原生的 Selector
  • 一个NioEventLoop相当于一个线程

Netty版本

<dependency>
    <groupId>io.nettygroupId>
    <artifactId>netty-allartifactId>
    <version>4.1.63.Finalversion>
dependency>

你可能感兴趣的:(Netty,netty)