Netty中最核心的就是Reactor线程,对应Netty中的代码就是NioEventLoop。NioEventLoop是通过NioEventLoopGroup进行维护的,所以在介绍NioEventLoop前我们先介绍一下NioEventLoopGroup
关于我:http://huangth.com
GitHub地址:https://github.com/RobertoHuang
免责声明:本系列博客并非原创,主要借鉴和抄袭闪电侠,占小狼等知名博主博客。如有侵权请及时联系
NioEventLoopGroup在客户端/服务端初始化时创建
EventLoopGroup bossGroup = new 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
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实例
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(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的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的执行过程