死磕Netty源码之Reactor线程模型详解(一)NioEventLoop的启动

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/RobertoHuang/article/details/80958749

前言

Netty中最核心的就是Reactor线程,对应Netty中的代码就是NioEventLoop。NioEventLoop是通过NioEventLoopGroup进行维护的,所以在介绍NioEventLoop前我们先介绍一下NioEventLoopGroup

关于我:http://huangth.com

GitHub地址:https://github.com/RobertoHuang

免责声明:本系列博客并非原创,主要借鉴和抄袭闪电侠占小狼等知名博主博客。如有侵权请及时联系

NioEventLoopGroup创建

NioEventLoopGroup在客户端/服务端初始化时创建

EventLoopGroup bossGroup = new NioEventLoopGroup();

以下是NioEventLoopGroup继承关系图
NioEventLoopGroup的继承关系图

顶层接口是Executor是JDK中的类可知EventLoopGroup支持执行一个异步任务,接下来是ScheduledExecutorService看名字可知子类将支持任务的调度执行,接下来我们继续跟进EventLoopGroup的构造方法,它最终调用到MultithreadEventLoopGroup

protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
    if (executor == null) {
         executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }

    children = new EventExecutor[nThreads];

    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        children[i] = newChild(executor, args);
        success = true;
    }

    chooser = chooserFactory.newChooser(children);
}

通过调用链不难发现这里传递进来的executor为null。所以在MultithreadEventExecutorGroup构造方法中主要做了3件事

1.创建线程执行器ThreadPerTaskExecutor
2.创建NioEventLoop数组
3.初始化NioEventLoop数组
4.初始化线程选择器

创建线程执行器

线程执行器通过调用ThreadPerTaskExecutor构造函数进行初始化

executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());

在调用构造函数时传递的参数ThreadFactory为DefaultThreadFactory实例

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

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        if (threadFactory == null) {
            throw new NullPointerException("threadFactory");
        }
        this.threadFactory = threadFactory;
    }

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

它主要功能是通过ThreadFactory新建线程并执行任务,Netty中的默认NIO线程都是由DefaultThreadFactory.newThread()创建

public Thread newThread(Runnable r) {
    Thread t = newThread(new DefaultRunnableDecorator(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;
}

Netty对线程进行了一层封装及一些属性设置,这些参数是在DefaultThreadFactory的构造方法中被初始化的

public DefaultThreadFactory(String poolName, boolean daemon, int priority, ThreadGroup threadGroup) {
    // ...
    prefix = poolName + '-' + poolId.incrementAndGet() + '-';
    this.daemon = daemon;
    this.priority = priority;
    this.threadGroup = threadGroup;
}

我们重点关注一下新建线程的线程名prefix + nextId.incrementAndGet()到底是什么?跟踪代码发现prefix的规则是poolName和poolId(自增)通过’-‘连接起来的,通过调用链可以知道此处的poolName为’nioEventLoopGroup’。所以Netty新建的NIO线程默认名称为nioEventLoopGroup-nioEventLoopId-自增ID,如nioEventLoopGroup-2-1

创建NioEventLoop数组

children = new EventExecutor[nThreads];

关于nThreads如果用户显示指定nThreads数量那就按照用户指定的设置,否则这个值将是CPU核数的两倍。由于我们在创建NioEventLoopGroup时未传递任何参数,所以此处的nThreads为2倍的CPU核数,相关代码如下

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

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

children是EventExecutor数组,但是这里数组中的每个元素其实都是NioEventLoop实例

初始化NioEventLoop数组

children数组的初始化是在以下代码中完成的

children[i] = newChild(executor, args);

我们跟进newChild()它最后调用的是NioEventLoopGroup的newChild方法

protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    return new NioEventLoop(this, executor, (SelectorProvider) args[0], ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}

newChild方法最后调用了NioEventLoop的构造方法,以下是NioEventLoop继承关系图
NioEventLoop继承关系图

NioEventLoop的构造方法代码如下

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
    provider = selectorProvider;
    final SelectorTuple selectorTuple = openSelector();
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;
}

可以看到这里打开了一个Selector,也就是说每一个NioEventLoop都与一个Selector绑定

初始化线程选择器

初始化线程选择器在如下代码中完成

chooser = chooserFactory.newChooser(children);

继续跟进newChooser方法,代码如下

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

我们可以发现Netty通过判断线程个数nThreads是否为2的幂次方来选择chooser,接下来我们分析两个chooser

private final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
    @Override
    public EventExecutor next() {
        // 利用2的N次方法的特点,使用&比求余更快
        return children[childIndex.getAndIncrement() & children.length - 1];
    }
}

private final class GenericEventExecutorChooser implements EventExecutorChooser {
    @Override
    public EventExecutor next() {
        // 使用求余方式
        return children[Math.abs(childIndex.getAndIncrement() % children.length)];
    }
} 

至此我们已经完成了NioEventLoopGroup的创建,并在NioEventLoopGroup创建过程中完成了NioEventLoop的初始化工作

NioEventLoop启动

NioEventLoop的run方法是Reactor线程的主体,在第一次添加任务的时候被启动

public void execute(Runnable task) {
    // ...

    boolean inEventLoop = inEventLoop();
    if (inEventLoop) {
        addTask(task);
    } else {
        startThread();
        addTask(task);
    }

    // ...
}

外部线程在往任务队列里面添加任务的时候执行startThread(),代码如下

private void startThread() {
    if (state == ST_NOT_STARTED) {
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            doStartThread();
        }
    }
}

在startThread()方法中Netty会判断Reactor线程有没有被启动,如果还未启动则先通过CAS方式将STATE_UPDATER的值设置为ST_START然后启动线程(CAS确保下次有新任务执行的时候再调用这个方法不会再次去启动线程),接下来分析doStartThread()

private void doStartThread() {
    ...
    executor.execute(new Runnable() {
        @Override
        public void run() {
            thread = Thread.currentThread();
            ...
                SingleThreadEventExecutor.this.run();
            ...
        }
    }
}

在这里Netty没有使用传统的线程创建方式来执行run方法,而是通过一个线程执行器executor来执行,其实是因为executor底层对线程做了一层优化,此处的executor就是上文中介绍到的ThreadPerTaskExecutor,它在每次执行execute方法的时候都会通过DefaultThreadFactory创建一个FastThreadLocalThread线程,而这个线程就是Netty中的Reactor线程实体

至此NioEventLoop启动完毕,在下一篇博客中将介绍NioEventLoop的执行过程

猜你喜欢

转载自blog.csdn.net/RobertoHuang/article/details/80958749