Netty源码分析基于主流的Netty4.x版本。
之前文章中经常出现如下代码
channel.eventLoop().execute(()->{})
本文就来分析下,execute都做了哪些操作。
public void execute(Runnable task) {
...
// 当前线程是否是eventLoop关联的线程
// 这里返回false,可能是线程不一致或者是EventLoop还未关联线程
boolean inEventLoop = inEventLoop();
// 将任务添加到taskQueue中
addTask(task);
if (!inEventLoop) {
// 启动一个线程
startThread();
// 线程池异常的处理
...
}
...
}
一.startThread
private void startThread() {
// 校验当前线程池状态是否还未启动
if (state == ST_NOT_STARTED) {
// CAS 比较更新当前为已启动状态
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
doStartThread();
...
}
}
}
更新完线程池状态,进入实际启动线程的操作
private void doStartThread() {
assert thread == null;
// 这里的executor指的是ThreadPerTaskExecutor
executor.execute(new Runnable() {
@Override
public void run() {
// 获取当前线程
thread = Thread.currentThread();
...
// 更新最近执行任务时间
updateLastExecutionTime();
try {
// 这里执行NioEventLoop的run方法,是个死循环
SingleThreadEventExecutor.this.run();
success = true;
}
...
}
});
}
这里的executor实际是ThreadPerTaskExecutor,是什么时候设置的呢?
还记得之前的分析NioEventLoopGroup的文章中,如下片段
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 ++) {
...
// 将实例的executor传入NioEventLoop
children[i] = newChild(executor, args);
}
...
}
二.run
进入NioEventLoop的run方法,咋一看代码好多,主要做了如下几件事:
- selelct/selectNow: 获取channel事件
- processSelectedKeys: 处理channel事件
- runAllTasks:执行队列中的任务
protected void run() {
for (;;) {
try {
try {
// 获取选择策略
// hasTasks是判断taskQueue或者tailTasks中的是否任务
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
// 堵塞轮训channel事件
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
//唤醒正在堵塞的selector
selector.wakeup();
}
default:
}
}
cancelledKeys = 0;
needsToSelectAgain = false;
// IO操作与任务执行时间比例
final int ioRatio = this.ioRatio;
// 这里的IO比例,100表示执行所有任务,否则给一个超时时间
if (ioRatio == 100) {
try {
// 处理channel轮训到的事件
processSelectedKeys();
} finally {
// 运行任务
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
// 处理channel轮训到的事件
processSelectedKeys();
} finally {
// 计算能执行的时长
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
}
...
}
}
calculateStrategy: 当前有任务,则立即调用selectNow获取准备好IO操作的channel数,否则返回SelectStrategy.SELECT
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
三. select
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
// delayNanos获取scheduledTaskQueue是否有到期任务,有的话返回0,否则最接近的到期时间
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
// 这里说明有到期定时任务
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
// 有任务 && CAS更新wakenUp 成功
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
// 堵塞一定时间,获取channel事件
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
// 有任务可以处理,退出循环
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
// 线程中断退出循环
if (Thread.interrupted()) {
...
break;
}
long time = System.nanoTime();
// 表示上次select到现在过的时间,是否确实阻塞了timeoutMillis
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
selectCnt = 1;
}
// 如果select没有堵塞这么久,说明发生了空轮训
else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
// 空轮训次数超出SELECTOR_AUTO_REBUILD_THRESHOLD,则绑定一个新的selector
selector = selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
...
}
...
}
四.processSelectedKeys
之前的NioEventLoopGroup的文章中,提到对selector优化,这里就是体现的地方。
private void processSelectedKeys() {
if (selectedKeys != null) {
// 优化是从数组取值
processSelectedKeysOptimized();
} else {
// 不优化是从set中获取值
processSelectedKeysPlain(selector.selectedKeys());
}
}
执行优化的方法
private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
// 这里使用优化为数组结构,快速获取值
final SelectionKey k = selectedKeys.keys[i];
// 便于GC
selectedKeys.keys[i] = null;
final Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
...
}
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
...
try {
// 当前事件
int readyOps = k.readyOps();
// 连接事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
// 移除感兴趣OP_CONNECT事件
// 一个channel只有首次连接需要处理,不移除后续select会直接返回不堵塞
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// 写事件
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
// 读事件、接收事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
}
...
}
这里看下接收事件,如何体现主从Reactor模型。
read最终会调用 NioMessageUnsafe的read方法
public void read() {
...
try {
try {
do {
// NioSocketChannel 包装readBuf
int localRead = doReadMessages(readBuf);
,..
} while (allocHandle.continueReading());
}
...
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
// 触发读事件通知
pipeline.fireChannelRead(readBuf.get(i));
}
...
}
...
}
进入NioServerSocketChannel的doReadMessages,这里会包装为NioSocketChannel
protected int doReadMessages(List<Object> buf) throws Exception {
// 获取新连接
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
// 将新连接封装为Netty的NioSocketChannel
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
...
}
return 0;
}
fireChannelRead 会发布读事件通知,这时之前设置的ServerBootstrapAcceptor会触发channelRead。
这个方法会将channel注册到workGroup中去处理。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
...
try {
// 注册channel到childGroup,实现了Acceptor接收连接,workGroup处理IO的过程
childGroup.register(child).addListener(()->{});
} catch (Throwable t) {
...
}
}
回顾下接收事件触发流程:
- 包装新连接为NioSocketChannel
- 发布读事件通知
- ServerBootstrapAcceptor接收到读事件,将channel注册到workGroup中
五.runAllTasks
protected boolean runAllTasks(long timeoutNanos) {
// 将定时任务添加到taskQueue
fetchFromScheduledTaskQueue();
// 获取一个任务
Runnable task = pollTask();
if (task == null) {
// 执行tailTasks中的任务
afterRunningAllTasks();
return false;
}
// 截止时间,ScheduledFutureTask.nanoTime()获取当前时间
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
// 记录执行的任务数
long runTasks = 0;
long lastExecutionTime;
for (;;) {
safeExecute(task);
runTasks ++;
// 每64个任务检查一次超时
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
// 超时接收
if (lastExecutionTime >= deadline) {
break;
}
}
// 获取下一个任务
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
// 执行tailTasks中的任务
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}