下面来分析NioEventLoop
首先我们得明确几个问题,NioEventLoop中都有什么
NioEventLoop包含三个 selector、线程、任务队列
下面来看看源码
selector创建是在他的构造方法中有一段final SelectorTuple selectorTuple = openSelector();代码
跟进去查看
private SelectorTuple openSelector() {
final Selector unwrappedSelector;
try {
unwrappedSelector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
}
也是由一个SelectorProvider来提供的,可能大家会有疑问,为什么会在源码里有两个selector
private Selector selector;
private Selector unwrappedSelector;
原因是因为我们的Selector里面有一个selectedKeys集合,里面存着将来发生关注事件的信息,而他使用的是set集合来存储,遍历起来很慢,所以netty就把他改成了数组形式来存储。
try {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
...
}
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
if (cause != null) {
return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
if (cause != null) {
return cause;
}
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
} catch (NoSuchFieldException e) {
return e;
} catch (IllegalAccessException e) {
return e;
}
这些代码的步骤就是取出selectedKeys集合然后通过netty自己写的反射工具类来让就算是私有的selectedKeys也可以通过反射来改变,比较粗暴,然后就通过设置把原有的基于set实现的改成了netty自己的基于数组实现的了,所以这也是为什么有两个selector,一个是原始的一个是基于数组的,这样就可以在遍历key是提高性能。
下面再来说线程
NioEventLoop中的线程在他的父类SingleThreadEventExecutor中
为了让大家看得更清楚写了一个例子,来看看这个线程是在什么时候被创建的
public class TestThread {
public static void main(String[] args) {
EventLoop eventLoop=new NioEventLoopGroup().next();
eventLoop.execute(() -> System.out.println("aaa"));
}
}
在execute上打上一个断点一样的启用debug模式进入
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
}
if (reject) {
reject();
}
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
首先是判断会不会是空任务,然后判断是不是NioEventLoop
这里顺便也看看他是如何判断的,点进inEventLoop();方法
public boolean inEventLoop() {
return inEventLoop(Thread.currentThread());
}
传入的是当前线程,在这里也就是主线程继续进去
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
目前这里的this.thread为空所以结果为false,也就表示不是NioEventLoop中的线程
把这个任务添加到任务队列中,稍后解释
然后就会进入到if里面调用startThread();方法
private void startThread() {
if (state == ST_NOT_STARTED) {
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);
}
}
}
}
}
第一步就是判断是不是第一次来启动线程,答案是true所以进入,然后就会把状态改成启动过了,保证第一次以后都无法启动线程,接下来看doStartThread();方法
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
}
assert thread == null;这个线程指的也就是eventLoop中的线程,然后由一个执行器来提交一个任务,这个executor其实也就是一个 线程池,里面只有一个线程,然后就会进入run方法里面,接着就会吧当前执行任务的线程赋值给eventLoop中的线程,也就是nioEventLoopGroup中的线程,可以打开看看
接下来就会进入到SingleThreadEventExecutor.this.run();方法中
protected void run() {
for (;;) {
try {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
} catch (IOException e) {
rebuildSelector0();
handleLoopException(e);
continue;
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
在这里面就会发现是一个死循环,一直重复的循环调用有没有Io、普通任务、定时任务,如果有则执行,如果没有任务时则会阻塞住,因为selector有一个阻塞住的select方法.
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks()))可以点进去查看
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
这里也就是判断有没有任务,如果有任务则执行selectSupplier.get()调用到
int selectNow() throws IOException {
int var1;
try {
var1 = this.selector.selectNow();
} finally {
if (this.wakenUp.get()) {
this.selector.wakeup();
}
}
return var1;
}
selectNow();也就是立即从当前的事件中取出io任务,如果没有则返回0;
如果没任务则会阻塞住,为了不浪费cpu资源,那他会阻塞多久呢,我们点进去看看
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
if (Thread.interrupted()) {
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely because " +
"Thread.currentThread().interrupt() was called. Use " +
"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
}
selectCnt = 1;
break;
}
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
selector = selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
selectCnt - 1, selector);
}
}
}
}
首先看到int selectedKeys = selector.select(timeoutMillis);也就是这里设置的阻塞时间,就得获取timeoutMillis超时时间
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
获取当前时间,selectDeadLineNanos为截止时间,他是通过当前时间加上delayNanos(currentTimeNanos)
protected long delayNanos(long currentTimeNanos) {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
if (scheduledTask == null) {
return SCHEDULE_PURGE_INTERVAL;
}
return scheduledTask.delayNanos(currentTimeNanos);
}
这里会先判断有无定时任务,这里先考虑无定时任务的情况,也就是1秒,如果有定时任务则为定时任务
private static final long SCHEDULE_PURGE_INTERVAL = TimeUnit.SECONDS.toNanos(1);
所以截止时间为当前时间加一秒
所以得出超时时间为1.5秒,也就是说只会阻塞1.5秒,然后就会继续循环,也有几种情况
比如让我们当前时间大于了截止时间得到的超时时间小于零了就会获取当前事件任务,或者是有普通任务则也会打断循环,情况挺多的,这里就不一一举例了。
在这里说下我们的selector有一个空轮询的Bug在linux环境下,而在这个bug会让这个线程一直在死循环,就算没有任务也会不阻塞住,导致cpu占率升高,一个线程还好,如果多了就很严重了,而netty为了解决这个bug就设置了一个次数selectCnt这个变量
else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
selector = selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}
再来看看SELECTOR_AUTO_REBUILD_THRESHOLD变量是多少,看我们这个类的静态块里面
int selectorAutoRebuildThreshold = SystemPropertyUtil.getInt("io.netty.selectorAutoRebuildThreshold", 512);
if (selectorAutoRebuildThreshold < MIN_PREMATURE_SELECTOR_RETURNS) {
selectorAutoRebuildThreshold = 0;
}
SELECTOR_AUTO_REBUILD_THRESHOLD = selectorAutoRebuildThreshold;
发现他是512,也就是所如果空循环了512次,就会跳出循环。
下一篇再来分析他是如何区分事件的