目录
1、NioEventLoopGroup
1.1 类图
1.2 初始化
1.创建线程执行器
2.创建EventLoop,并存储到EventExecutor类型的数组中
3.创建线程选择器
2、NioEventLoop
2.1 类图
2.2 selector
2.3 run
1.轮询io事件
2.处理轮询到的key
3.执行任务队列中的task
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
这里创建了两个group, 我们提过这是boss线程组和worker线程组, 其实这两个线程组就相当于两个NioEventLoop的集合, 默认每个NioEventLoopGroup创建时, 如果不传入线程数, 会创建cpu核数*2个NioEventLoop线程, 其中boss线程通过轮询处理Server的accept事件, 而完成accept事件之后, 就会创建客户端channel, 通过一定的策略, 分发到worker线程进行处理, 而worker线程, 则主要用于处理客户端的读写事件。
NioEventLoopGroup 是一个线程池,继承了 MultithreadEventLoopGroup
从构造函数入手
跟进super, 进入了其父类MultithreadEventExecutorGroup的构造方法中:这里我们看到, 如果传入的线程数量参数为0, 则会给一个默认值, 这个默认值就是两倍的CPU核数。
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}
}
继续跟代码之后, 我们就看到了创建NioEventLoop的真正逻辑, 在MultithreadEventExecutorGroup类的构造方法中,创建了 nThreads 个子线程池。
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
if (executor == null) {
//创建一个新的线程执行器(1)
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
// 创建子线程池数组,构造NioEventLoop(2)
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) {
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;
}
}
}
}
}
//创建线程选择器(3)
chooser = chooserFactory.newChooser(children);
final FutureListener
这边将代码主要分为三个步骤:
这里有个new DefaultThreadFactory()创建一个DefaultThreadFactory对象, 这个对象作为参数传入ThreadPerTaskExecutor的构造函数, DefaultThreadFactory顾名思义, 是一个线程工厂, 用于创建线程的, 简单看下这个类的继承关系:
public class DefaultThreadFactory implements ThreadFactory{//类体}
这里继承了jdk底层ThreadFactory类, 用于创建线程
我们继续跟进ThreadPerTaskExecutor的类中:
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();
}
}
这个类非常简单, 继承了jdk的Executor类, 从继承关系中我就能猜想到, 而这个类就是用于开启线程的线程执行器,再看重写的 execute(Runnable command) 方法, 传入一个任务, 然后由threadFactory对象创建一个线程执行该任务。这个execute(Runnable command)方法, 其实就是用开开启NioEventLoop线程用的。
这样, 通过 executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()) 这种方式就返回了一个线程执行器Executor, 用于开启NioEventLoop线程
这里通过 children = new EventExecutor[nThreads] 初始化了children属性, 看下这个属性的定义:
private final EventExecutor[] children
这里的children是EventExecutor类型的数组, 其实就是NioEventLoop的集合, 因为NioEventLoop也是EventExecutor的子类
所以这里初始化了children数组, 大小为参数nThreads传入的线程数量, 默认为cpu核数的两倍
后面就是通过for循环来创建NioEventLoop线程,在循环体里通过 newChild() 创建NioEventLoop, 我们跟newChild(executor, args)方法
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0], ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
this是NioEventLoopGroup自身, executor就是上一小节讲到的线程执行器
跟一下NioEventLoop的构造方法,跟到父类SingleThreadEventExecutor构造方法:
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = Math.max(16, maxPendingTasks);
//初始化了线程执行器
this.executor = ObjectUtil.checkNotNull(executor, "executor");
//创建一个任务队列, 这个任务队列可以将不属于NioEventLoop线程的任务放到这个任务队列中, 通过NioEventLoop线程执行
taskQueue = newTaskQueue(this.maxPendingTasks);
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
chooser = chooserFactory.newChooser(children);
NioEventLoopGroup都绑定一个chooser对象, 作为线程选择器, 通过这个线程选择器, 为每一个channel分配不同的线程
我们看到newChooser(children)传入了NioEventLoop数组
我们跟到DefaultEventExecutorChooserFactory类中的newChooser方法:
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTowEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
这里通过 isPowerOfTwo(executors.length) 判断NioEventLoop数组的长度是不是2的倍数, 然后根据判断结果返回两种选择器对象, 这里使用到java设计模式的策略模式
根据这两个类的名字不难看出, 如果是2的倍数, 使用的是一种高性能的方式选择线程, 如果不是2的倍数, 则使用一种比较普通的线程选择方式。
private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTowEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
这个类实现了线程选择器的接口EventExecutorChooser, 构造方法中初始化了NioEventLoop线程数组
重点关注下next()方法, next()方法就是选择下一个线程的方法, 如果线程数是2的倍数, 这里通过按位与进行计算, 所以效率极高
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
}
这个类同样实现了线程选择器的接口EventExecutorChooser, 并在造方法中初始化了NioEventLoop线程数组
再看这个类的next()方法, 如果线程数不是2的倍数, 则用绝对值和取模的这种效率一般的方式进行线程选择
NioEventLoop 是只有单个线程的线程池,但并不是一个纯粹的I/O线程,它除了负责I/O的读写之外,还兼顾处理以下两类任务:
系统Task:通过调用NioEventLoop的execute(Runnable task)方法实现,Netty有很多系统Task,创建它们的主要原因是:当I/O线程和用户线程同时操作网络资源时,为了防止并发操作导致的锁竞争,将用户线程的操作封装成Task放入消息队列中,由I/O线程负责执行,这样就实现了局部无锁化。
定时任务:通过调用NioEventLoop的schedule(Runnable command, long delay, TimeUnit unit)方法实现。
作为NIO框架的Reactor线程,NioEventLoop需要处理网络I/O读写事件,因此它必须聚合一个多路复用器对象。Selector的初始化非常简单,直接调用Selector.open()方法就能创建并打开一个新的Selector。Netty对Selector的selectedKeys进行了优化,用户可以通过io.netty.noKeySetOptimization开关决定是否启用该优化项。默认不打开selectedKeys优化功能。
Selector selector;
private SelectedSelectionKeySet selectedKeys;
private final SelectorProvider provider;
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider) {
super(parent, executor, false);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
provider = selectorProvider;
//初始化selector
selector = openSelector();
}
private Selector openSelector() {
final Selector selector;
try {
////调用jdk底层的api,通过provider.openSelector()创建并打开多路复用器
selector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
//如果没有开启selectedKeys优化开关,就立即返回。
if (DISABLE_KEYSET_OPTIMIZATION) {
return selector;
}
//如果开启了优化开关,需要通过反射的方式从Selector实例中获取selectedKeys和publicSelectedKeys,
//将上述两个成员变量设置为可写,通过反射的方式使用Netty构造的selectedKeys包装类selectedKeySet将原JDK的selectedKeys替换掉。
try {
//用这个数据结构替换原生的SelectionKeySet
SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
//通过反射拿到sun.nio.ch.SelectorImpl这个类的class对象
Class> selectorImplClass = Class.forName("sun.nio.ch.SelectorImpl", false, ClassLoader.getSystemClassLoader());
//判断拿到的是不是class对象并且是不是Selector的实现类,如果不是他的实现, 就直接返回原生select
if (!selectorImplClass.isAssignableFrom(selector.getClass())) {
return selector;
}
//通过反射拿到selectedKeys和publicSelectedKeys两个属性, 默认这两个属性底层都是hashSet方式实现的
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
//设置成可修改的
selectedKeysField.setAccessible(true);
publicSelectedKeysField.setAccessible(true);
//将selector的这两个属性替换成Netty的selectedKeySet
selectedKeysField.set(selector, selectedKeySet);
publicSelectedKeysField.set(selector, selectedKeySet);
selectedKeys = selectedKeySet;
logger.trace("Instrumented an optimized java.util.Set into: {}", selector);
} catch (Throwable t) {
selectedKeys = null;
logger.trace("Failed to instrument an optimized java.util.Set into: {}", selector, t);
}
return selector;
}
selector在select()操作时候, 会通过selector.selectedKeys()操作返回一个Set
netty对这个set进行了处理, 使用SelectedSelectionKeySet的数据结构进行替换, 当在select()操作时将key存入一个SelectedSelectionKeySet的数据结构中。
简单跟一下SelectedSelectionKeySet这个类的构造方法:
SelectedSelectionKeySet() {
keys = new SelectionKey[1024];
}
说明这类其实底层是通过数组实现的, 通过操作数组下标会有更高的效率。看下其中重要的方法,add表示添加一个SelectionKey。remove()方法, contains()方法都返回了false, 说明其不支持删除方法和包含方法。
@Override
public boolean add(SelectionKey o) {
if (o == null) {
return false;
}
keys[size++] = o;
if (size == keys.length) {
increaseCapacity();
}
return true;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
NioEventLoop 的核心方法是 run 方法
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
//轮询io事件(1)
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
//ioRatio主要是用来控制processSelectedKeys()方法执行时间和任务队列执行时间的比例, 其中ioRatio默认是50, 所以会走到下一步else
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
//记录下开始时间
final long ioStartTime = System.nanoTime();
try {
//处理轮询到的key(2)
processSelectedKeys();
} finally {
//计算耗时
final long ioTime = System.nanoTime() - ioStartTime;
//执行task(3)
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
分为以下3步
1. 轮询io事件
2. 处理轮询到的key
3. 执行任务队列中的task
首先switch块中默认会走到SelectStrategy.SELECT中, 执行select(wakenUp.getAndSet(false))方法
参数wakenUp.getAndSet(false)代表当前select操作是未唤醒状态
进入到select(wakenUp.getAndSet(false))方法中:
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
//当前系统的纳秒数
long currentTimeNanos = System.nanoTime();
//截止时间=当前时间+队列第一个任务剩余执行时间,因为超过之后定时任务不能严格按照预定时间执行,
//delayNanos(currentTimeNanos)代表距定时任务中第一个任务剩余多长时间,
//其中定时任务队列是已经按照执行时间有小到大排列好的队列, 所以第一个任务则是最近需要执行的任务, selectDeadLineNanos就代表了当前操作不能超过的时间。
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
//阻塞时间(毫秒)=(截止时间-当前时间+0.5毫秒),除以1000000表示将计算的时间转化为毫秒数
//最后算出的时间就是selector操作的阻塞时间, 并赋值到局部变量的timeoutMillis中
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
//时间已经超过了最后截止时间+0.5毫秒, selectCnt == 0 代表没有进行select操作, 满足这两个条件, 则执行selectNow()之后, 将selectCnt赋值为1之后跳出循环
selector.selectNow();
selectCnt = 1;
}
break;
}
//没超过截止时间
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
//hasTasks()这里是判断当前NioEventLoop所绑定的taskQueue是否有任务,
//如果有任务, sa则执行selectNow()之后, 将selectCnt赋值为1之后跳出循环(跳出循环之后去执行任务队列中的任务)
selector.selectNow();
selectCnt = 1;
break;
}
//如果没有满足上述条件, 就会执行如下,进行阻塞式轮询, 并且自增轮询次数
int selectedKeys = selector.select(timeoutMillis);
//轮询次数
selectCnt ++;
//如果轮询到一个事件(selectedKeys != 0), 或者当前select操作需要唤醒(oldWakenUp),
//或者在执行select操作时已经被外部线程唤醒(wakenUp.get()),
//或者任务队列已经有任务(hasTask), 或者定时任务队列中有任务(hasScheduledTasks())
//如果满足以上,就跳出循环
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
//省略
//记录下当前时间
long time = System.nanoTime();
//当前时间-开始时间>=超时时间(条件成立, 说明已经完整的执行完成了一个阻塞的select()操作, 条件不成立, 有可能发生空轮询)
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
//代表已经进行了一次阻塞式select操作, 操作次数重置为1
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
//省略日志代码
//如果空轮询的次数大于一个阈值(512), 解决空轮询的bug
rebuildSelector();
selector = this.selector;
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
//代码省略
} catch (CancelledKeyException e) {
//省略
}
}
如果此条件不成立, 说明没有完整执行select()操作, 可能触发了一次空轮询, 根据前一个selectCnt++这步我们知道, 每触发一次空轮询selectCnt都会自增
之后会进入第二个判断 SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD
其中SELECTOR_AUTO_REBUILD_THRESHOLD默认是512, 这个判断意思就是空轮询的次数如果超过512次, 则会认为是发生了epoll bug, 这样会通过rebuildSelector()方法重新构建selector, 然后将重新构建的selector赋值到局部变量selector, 执行一次selectNow(), 将selectCnt初始化1, 跳出循环
ioRatio主要是用来控制processSelectedKeys()方法执行时间和任务队列执行时间的比例, 其中ioRatio默认是50, 所以会走到下一步else,首先通过 final long ioStartTime = System.nanoTime() 记录下开始时间, 再通过processSelectedKeys()方法处理轮询到的key。
我们跟到processSelectedKeys()方法中:
private void processSelectedKeys() {
if (selectedKeys != null) {
//如果selectedKeys被netty优化过,走这里
processSelectedKeysOptimized();
} else {
//如果selectedKeys没有被优化过,到这里
processSelectedKeysPlain(selector.selectedKeys());
}
}
private void processSelectedKeysOptimized() {
//通过for循环遍历数组
for (int i = 0; i < selectedKeys.size; ++i) {
//拿到当前的selectionKey
final SelectionKey k = selectedKeys.keys[i];
//将当前引用设置为null
selectedKeys.keys[i] = null;
//获取channel(NioSeverSocketChannel)
final Object a = k.attachment();
//判断channel是不是AbstractNioChannel, 通常情况都是AbstractNioChannel。
//如果是AbstractNioChannel, 则调用processSelectedKey()方法处理io事件
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask task = (NioTask) a;
processSelectedKey(k, task);
}
if (needsToSelectAgain) {
// null out entries in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
//获取到channel中的unsafe
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
//验证key是否合法的
if (!k.isValid()) {
//如果这个key不是合法的, 说明这个channel可能有问题
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
}
try {
//如果是合法的, 拿到key的io事件
int readyOps = k.readyOps();
//链接事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
//写事件
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
//读事件和接受链接事件
//如果当前NioEventLoop是work线程的话, 这里就是op_read事件
//如果是当前NioEventLoop是boss线程的话, 这里就是op_accept事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
//然后会通过channel绑定的unsafe对象执行read()方法用于处理链接或者读写事件
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
protected boolean runAllTasks(long timeoutNanos) {
//从定时任务队列中聚合任务,也就是将定时任务中找到可以执行的任务添加到taskQueue中
fetchFromScheduledTaskQueue();
//从普通taskQ里面拿一个任务
Runnable task = pollTask();
//task为空, 则直接返回
if (task == null) {
//跑完所有的任务执行收尾的操作
afterRunningAllTasks();
return false;
}
//如果队列不为空
//首先算一个截止时间(+50毫秒, 因为执行任务, 不要超过这个时间)
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
//执行每一个任务
for (;;) {
safeExecute(task);
//标记当前跑完的任务
runTasks ++;
//当跑完64个任务的时候, 会计算一下当前时间
if ((runTasks & 0x3F) == 0) {
//定时任务初始化到当前的时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
//如果超过截止时间则不执行(nanoTime()是耗时的)
if (lastExecutionTime >= deadline) {
break;
}
}
//如果没有超过这个时间, 则继续从普通任务队列拿任务
task = pollTask();
//直到没有任务执行
if (task == null) {
//记录下最后执行时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
//收尾工作
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
private boolean fetchFromScheduledTaskQueue() {
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
//从定时任务队列中抓取第一个定时任务
//寻找截止时间为nanoTime的任务
Runnable scheduledTask = pollScheduledTask(nanoTime);
//如果该定时任务队列不为空, 则塞到普通任务队列里面
while (scheduledTask != null) {
//如果添加到普通任务队列过程中失败
if (!taskQueue.offer(scheduledTask)) {
//则重新添加到定时任务队列中
scheduledTaskQueue().add((ScheduledFutureTask>) scheduledTask);
return false;
}
//继续从定时任务队列中拉取任务
//方法执行完成之后, 所有符合运行条件的定时任务队列, 都添加到了普通任务队列中
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
参考文献:
https://www.cnblogs.com/xiangnan6122/p/10203169.html