Netty源码分析-EventLoop(上)
前言
由于整个NioEventLoop
类过大,其中还有很多从父类继承的方法。所以在这里通过以问题驱动的方式,对重要部分进行逐步分析。
组成部分
-
Selector
private Selector selector; private Selector unwrappedSelector;
-
线程
在父类
SingleThreadEventLoop
的父类SingleThreadEventExecutor
中:private volatile Thread thread; // EventLoop用来执行任务处理IO事件的线程,单线程 private final Executor executor; // 执行器,可以理解为单线程的线程池
这两个使用的是同一个线程,executor有更多的功能,比如提交任务,添加定时功能等等。
-
任务队列
在父类
SingleThreadEventLoop
的父类SingleThreadEventExecutor
中:private final Queue<Runnable> taskQueue; // 存放尚未被处理的任务
在父类
SingleThreadEventLoop
的父类SingleThreadEventExecutor
的父类AbstractScheduledEventExecutor
中:PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue; // 用来处理定时任务的任务队列
Selector在什么时候被创建?
Selector是在NioEventLoop
的构造方法中被创建的:
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler, EventLoopTaskQueueFactory queueFactory) {
super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory), rejectedExecutionHandler);
this.provider = (SelectorProvider)ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
this.selectStrategy = (SelectStrategy)ObjectUtil.checkNotNull(strategy, "selectStrategy");
// 创建Selector
NioEventLoop.SelectorTuple selectorTuple = this.openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
}
在构造方法中调用了openSelector()
来创建Selector:
private NioEventLoop.SelectorTuple openSelector() {
final AbstractSelector unwrappedSelector;
try {
// provider.openSelector() 即创建了一个Selector对象
unwrappedSelector = this.provider.openSelector();
} catch (IOException var7) {
throw new ChannelException("failed to open a new selector", var7);
}
...
}
原生的创建Selector的方式为:
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
由此可见,两者本质上是一样的。
为什么有两个Selector对象?
在上述openSelector()
方法中:
。private NioEventLoop.SelectorTuple openSelector() {
final AbstractSelector unwrappedSelector;
try {
// 创建给unwrappedSelector 的Selector是真正的Nio底层的Selector
unwrappedSelector = this.provider.openSelector();
} catch (IOException var7) {
throw new ChannelException("failed to open a new selector", var7);
}
...
}
因为Nio原生的Selector,它的内部有一个SelectionKeys集合,将来发生的事件需要通过集合中获取事件的信息。这个集合默认的实现是Set。Set集合底层是hash表,所以遍历的性能并不高,所以Netty将集合的实现替换成为了数组实现,以此来提升效率。体现源码如下:
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
// 通过反射拿到unwrappedSelector中的selectedKeys属性`
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
long publicSelectedKeysFieldOffset = PlatformDependent.objectFieldOffset(publicSelectedKeysField);
if (selectedKeysFieldOffset != -1L && publicSelectedKeysFieldOffset != -1L) {
PlatformDependent.putObject(unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
PlatformDependent.putObject(unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
return null;
}
}
// 暴力反射,修改私有属性
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
if (cause != null) {
return cause;
} else {
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
if (cause != null) {
return cause;
} else {
// 替换为基于数组的selectedKeys实现
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
}
}
} catch (NoSuchFieldException var7) {
return var7;
} catch (IllegalAccessException var8) {
return var8;
}
}
});
selectedKeys = selectedKeySet;
// 调用构造函数,创建unwrappedSelector与selector
return new SelectorTuple(unwrappedSelector,new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
其实性能并没有提升多少,但是在Netty中这样的优化细节还有很多。所以总体上来看提升的性能还是很可观的。
EventLoop的nio线程在何时启动?
NioEventLoop中的线程,在首次执行任务时,才会被创建,且只会被创建一次。
使用execute
执行任务时,会判断此任务是否为懒加载任务:
@Override
public void execute(Runnable task) {
// 检测传入的任务是否为空,为空会抛出NullPointerException
ObjectUtil.checkNotNull(task, "task");
// 执行任务
// 此处判断了任务是否为懒加载任务,wakesUpForTask的返回值只会为true
execute(task, !(task instanceof LazyRunnable) && wakesUpForTask(task));
}
execute( ) :
private void execute(Runnable task, boolean immediate) {
// 判断当前线程是否为NIO线程
// 判断方法为 return thread == this.thread;
// this.thread即为NIO线程,首次执行任务时,其为null
boolean inEventLoop = inEventLoop();
// 向任务队列taskQueue中添加任务
addTask(task);
// 当前线程不是NIO线程,则进入if语句
if (!inEventLoop) {
// 启动NIO线程的核心方法
startThread();
...
}
// 有任务需要被执行时,唤醒阻塞的NIO线程
if (!addTaskWakesUp && immediate) {
wakeup(inEventLoop);
}
}
进入startThread方法 :
private void startThread() {
// 查看NIO线程状态是否为未启动
// 该if代码块只会执行一次
// state一开始的值就是ST_NOT_STARTED , 值为1
// private volatile int state = ST_NOT_STARTED;
if (state == ST_NOT_STARTED) {
// 通过原子属性更新器将状态更新为启动(ST_STARTED),值为2
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);
}
}
}
}
}
进入doStartThread,真正创建NIO线程并执行任务:
private void doStartThread() {
assert thread == null;
// 创建NIO线程并执行任务
executor.execute(new Runnable() {
@Override
public void run() {
// thread即为NIO线程
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
// 执行NioEventLoop内部run方法,其中会不断寻找定时任务、IO事件等任务去实现
SingleThreadEventExecutor.this.run();
success = true;
}
...
});
}
因此,当首次调用execute的时候,nio线程才会启动。并且通过pirvate vloatile int stats = ST_NOT_STARTED;
标志位来控制线程只会启动一次。
提交普通任务是否会结束select阻塞?
在select()
方法中,如果一些条件成立,就会调用:int selectedKeys = selector.select(timeoutMillis);
会导致当前线程在没有IO事件的时候一直阻塞下去直到超过设定的超时时间。在Netty中没有使用selector.select();
是因为Netty中EventLoop中需要处理多种事件,如果调用此方法则在没有IO事件的时候将一直阻塞下去,从而不能达到处理其他事件任务的目的。
在execute()
方法中有一个wakeup(boolean inEventLoop)
方法,源码部分为:
protected void wakeup(boolean inEventLoop) {
// inEventLoop 是判断提交任务的线程是不是nio线程,只有不是同一个才会进入代码块, nio线程有自己的唤醒方法不用操心
// wakenUp是一个原子变量,因为selector.wakeUp()是一个重量级操作应该避免频繁调用,如果有多个线程都来提交任务,
// 为了避免频繁调用,使用compareAndSet()确定只有一个线程成功可以调用wakeUp()方法
if (!inEventLoop && wakenUp.compareAndSet(false,true)) {
// 唤醒nio线程,使其及时进行下一个任务的处理
this.selector.wakeup();
}
}
会,通过wakeup()
方法去唤醒select中nio线程