前言
还记得使用netty的时候会出现这么一段代码吗:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
EventLoopGroup在没看源码之前,我把他理解为是一个管理EventLoop的组,而EventLoop是netty封装好的线程池模型,不知道我这样想得对不对,分析一下源码。
正文
EventLoopGroup
EventLoopGroup的接口图
public interface EventExecutorGroup extends ScheduledExecutorService, Iterable<EventExecutor>
根据这个接口图和接口代码来看,EventExecutorGroup应当具有这两个属性:支持定时任务线程池、内部保存了多个EventExecutor
public interface EventLoopGroup extends EventExecutorGroup {
/**
* 获取下一个EventLoop
*/
@Override
EventLoop next();
ChannelFuture register(Channel channel);
ChannelFuture register(ChannelPromise promise);
@Deprecated
ChannelFuture register(Channel channel, ChannelPromise promise);
}
EventLoopGroup仅有几个注册方法和next方法,EventLoopGroup会调用next去选择一个EventLoop来调用,所以先看一下EventLoop以及子类的实现。
EventLoop
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
@Override
EventLoopGroup parent();
}
EventLoop接口只有一个方法,就是和EventLoopGroup关联
public abstract class AbstractEventLoop extends AbstractEventExecutor implements EventLoop {
protected AbstractEventLoop() { }
protected AbstractEventLoop(EventLoopGroup parent) {
super(parent);
}
@Override
public EventLoopGroup parent() {
return (EventLoopGroup) super.parent();
}
@Override
public EventLoop next() {
return (EventLoop) super.next();
}
}
它的一个子类,继承的AbstractEventExecutor,进入这个类看看
public abstract class AbstractEventExecutor extends AbstractExecutorService implements EventExecutor
AbstractExecutorService内部实现了线程池的submit等方法,是JAVA内部线程池的一个抽象实现,这里继承这个类也是想把EventLoop变成一个类似线程池的东西
public interface EventExecutor extends EventExecutorGroup {
@Override
EventExecutor next();
EventExecutorGroup parent();
boolean inEventLoop();
boolean inEventLoop(Thread thread);
<V> Promise<V> newPromise();
<V> ProgressivePromise<V> newProgressivePromise();
<V> Future<V> newSucceededFuture(V result);
<V> Future<V> newFailedFuture(Throwable cause);
}
EventExecutor比较主要的方法是inEventLoop(),表示当前线程是否为启动线程。
AbstractEventExecutor并没有太多实现细节,因为线程的提交方法已经交给了JAVA内部去实现,所以继续向下看
public abstract class AbstractScheduledEventExecutor extends AbstractEventExecutor {
private static final Comparator<ScheduledFutureTask<?>> SCHEDULED_FUTURE_TASK_COMPARATOR =
new Comparator<ScheduledFutureTask<?>>() {
@Override
public int compare(ScheduledFutureTask<?> o1, ScheduledFutureTask<?> o2) {
return o1.compareTo(o2);
}
};
static final Runnable WAKEUP_TASK = new Runnable() {
@Override
public void run() { }
};
/**
* 优先队列 通过比较时间 时间相同比较ID
*/
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
AbstractScheduledEventExecutor是定时任务接口的实现,可以看到这里定义了一个比较器,用于对ScheduledFutureTask的比较
@Override
public int compareTo(Delayed o) {
if (this == o) {
return 0;
}
ScheduledFutureTask<?> that = (ScheduledFutureTask<?>) o;
long d = deadlineNanos() - that.deadlineNanos();
if (d < 0) {
return -1;
} else if (d > 0) {
return 1;
} else if (id < that.id) {
return -1;
} else {
assert id != that.id;
return 1;
}
}
定时任务很好理解,先根据时间比较,时间相同根据id比较
final ScheduledFutureTask<?> peekScheduledTask() {
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
return scheduledTaskQueue != null ? scheduledTaskQueue.peek() : null;
}
获取一个queue顶的任务(最早的任务),使用了队列的peek方法
protected final Runnable pollScheduledTask(long nanoTime) {
assert inEventLoop();
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
if (scheduledTask == null || scheduledTask.deadlineNanos() - nanoTime > 0) {
return null;
}
scheduledTaskQueue.remove();
scheduledTask.setConsumed();
return scheduledTask;
}
pollScheduledTask的参数是时间,从queue拿出一个任务,通过对时间的比较来确定是否从队列中取出
以上是延迟任务的关键方法,定义了一个队列,所有的一切都在队列上进行操作;继续向下看:SingleThreadEventExecutor
SingleThreadEventExecutor内部才像一个线程池应该有的东西,比如生命周期等。
protected SingleThreadEventExecutor(
EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
this(parent, new ThreadPerTaskExecutor(threadFactory), addTaskWakesUp);
}
看一个普通的构造方法,传入了threadFactory用于创建线程,在netty内部创建的线程都是FastThreadLocalThread,用于支持FastThreadLocal
/**
* poll一个queue 不能是WAKEUP_TASK
*/
protected Runnable pollTask() {
assert inEventLoop();
return pollTaskFrom(taskQueue);
}
protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
for (;;) {
Runnable task = taskQueue.poll();
if (task != WAKEUP_TASK) {
return task;
}
}
}
获取一个task的方法,调用了队列的poll方法
关注一下takeTask方法
protected Runnable takeTask() {
assert inEventLoop();
if (!(taskQueue instanceof BlockingQueue)) {
throw new UnsupportedOperationException();
}
BlockingQueue<Runnable> taskQueue = (BlockingQueue<Runnable>) this.taskQueue;
for (;;) {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
if (scheduledTask == null) {
Runnable task = null;
try {
task = taskQueue.take();
if (task == WAKEUP_TASK) {
task = null;
}
} catch (InterruptedException e) {
// Ignore
}
return task;
} else {
long delayNanos = scheduledTask.delayNanos();
Runnable task = null;
if (delayNanos > 0) {
try {
task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
// Waken up.
return null;
}
}
if (task == null) {
fetchFromScheduledTaskQueue();
task = taskQueue.poll();
}
if (task != null) {
return task;
}
}
}
}
private boolean fetchFromScheduledTaskQueue() {
if (scheduledTaskQueue == null || scheduledTaskQueue.isEmpty()) {
return true;
}
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
for (;;) {
Runnable scheduledTask = pollScheduledTask(nanoTime);
if (scheduledTask == null) {
return true;
}
//普通任务队列没有空间则重新放回
if (!taskQueue.offer(scheduledTask)) {
scheduledTaskQueue.add((ScheduledFutureTask<?>) scheduledTask);
return false;
}
}
}
首先这个方法是阻塞方法,如果获取不到就会一直循环,具体步骤:
- 从定时任务获取队列获取最快的那个任务
- 如果没有定时任务,则获取普通任务;如果有定时任务,则按照定时任务的时间作为超时时间去普通队列获取。
- 如果在指定时间内没有获取到普通任务,需要定时任务拿出来放入普通任务队列,然后再获取(如果不进行次操作的话,当普通任务队列有任务,将永远执行不了定时任务)
protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
Runnable task = pollTaskFrom(taskQueue);
if (task == null) {
return false;
}
for (;;) {
safeExecute(task);
task = pollTaskFrom(taskQueue);
if (task == null) {
return true;
}
}
}
protected static void safeExecute(Runnable task) {
try {
task.run();
} catch (Throwable t) {
logger.warn("A task raised an exception. Task: {}", task, t);
}
}
执行所有任务,循环将任务取出来,然后运行。这里可以看出来,SingleThreadEventExecutor并没有像传统线程池一样,将任务取出来交给一个新的线程执行,而是用了串行的方式来执行任务
只看一波部分doStartThread方法,可以看出来核心调用run方法,而run是个抽象方法交给子类实现,直接快进到NioEventLoop
NioEventLoop里面有令人熟悉JAVA底层的东西了,Selector、SelectorProvider都是java nio的实现
构造方法里面执行了一个openSelector的方法,这个方法还比较复杂分为两部分看
NioEventLoop有一个配置是DISABLE_KEY_SET_OPTIMIZATION,表示是否开启键优化
因为在Selector内部,key是用Set来存储的,键优化就是针对这个而进行修改
调用了openSelector(),获取了一个选择器,当没有开启优化的时候直接返回,如果开启了键优化,就要执行如下操作:
- 手动加载sun.nio.ch.SelectorImpl这个类,并且创建一个SelectedSelectionKeySet,这个里面用数组的方式存储SelectionKey而不用Set,减少了hash碰撞带来的开销。
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
- 利用unsafe获取selectedKeys和publicSelectedKeys的偏移量,并且将数据put到SelectedSelectionKeySet这个类的数组中
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
// Let us try to use sun.misc.Unsafe to replace the SelectionKeySet.
// This allows us to also do this in Java9+ without any extra flags.
long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
long publicSelectedKeysFieldOffset =
PlatformDependent.objectFieldOffset(publicSelectedKeysField);
if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) {
PlatformDependent.putObject(
unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
PlatformDependent.putObject(
unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
return null;
}
// We could not retrieve the offset, lets try reflection as last-resort.
}
- 将selectedKeys和publicSelectedKeys权限改为可访问,把原有的set替换为数组
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);
看一下重写的run方法
nextScheduledTaskDeadlineNanos获取定时队列第一个任务的超时时间,然后检查普通队列是否有任务,没有就按照这个超时时间用select去阻塞获取。这里有个问题,如果定时任务超时时间还很长,这个select就会一直阻塞下去吗?当然不会,只要有新的任务提交,就会触发wakeup
protected void wakeup(boolean inEventLoop) {
if (!inEventLoop && nextWakeupNanos.getAndSet(AWAKE) != AWAKE) {
selector.wakeup();
}
}
只要通过execute提交一个task,就会触发wakeup让select不再阻塞
当select返回大于0,表示已经有事件准备好了,这里的selectCnt是用于解决Selector的空轮询BUG,若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,CPU使用率100%,如果这个selectCnt达到了最大值,则需要重新构造Selector
ioRatio是用于控制I/O操作的比率,像read、write这些操作就是I/O操作,而对于taskQueue的获取添加运行,任务的运行就叫非I/O操作
当I/O操作为100时候,就要先处理准备好的SelectedKeys
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
这里的逻辑是对应了开启键优化和未开启,其实都差不多,一个是利用iterator来遍历,一个是利用数组遍历。
针对attachment的不同有不同的处理逻辑,看一下processSelectedKey
根据readyOps不同做不同的操作,最终还是交给了Channel的unsafe进行处理
通过这个接口的继承,可以看到EventLoop具有线程池属性,而NioEventLoop内部使用了无锁化的操作,将任务串行化避免多线程锁竞争带来的性能影响,从表面上看串行化不能更好的应对并发问题,但是可以启动多个串行化的线程一起工作,这种局部串行化整体多线程的设计方式,相比于一个任务队列多个线程的模式更优
EventExecutorGroup
EventExecutorGroup是用于管理多个EventExecutor的类
EventExecutor next();
其中next方法用于选择一个EventExecutor,先看AbstractEventExecutorGroup的第一个抽象实现类
这个类没什么特点,内部全部方法都是调用next选择出一个EventExecutor,使用之前解析过的方法。那其实核心方法就是next方法的实现,继续向下看
MultithreadEventExecutorGroup内部有一个EventExecutor的数组以及一个Set,看构造方法
代码简单,步骤如下:
- 实例化Executor,默认为newDefaultThreadFactory,newThread源码如下:
@Override
public Thread newThread(Runnable r) {
Thread t = newThread(FastThreadLocalRunnable.wrap(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;
}
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
可以看到使用的是FastThreadLocalThread
- 根据线程数量创建EventExecutor数组
- 调用newChild方法来实例化EventExecutor,newChild是一个抽象方法,由子类实现
- 出错后的关闭操作
再看一下next方法
@Override
public EventExecutor next() {
return chooser.next();
}
这里调用了EventExecutorChooser的next方法,这个类有两个不同的实现方法:
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
检查线程数是不是2的n次方
- PowerOfTwoEventExecutorChooser
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
id自增然后通过&来进行选取,当executors的长度为2的n次方时,使用&的方式比%效率要高一些,这也是netty的优化
- GenericEventExecutorChooser
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
// will encounter this in practice.
private final AtomicLong idx = new AtomicLong();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
}
}
这就是普通的取余的方法
MultithreadEventExecutorGroup主要是定义了构造方法和next选择的方法,next主要思想就是按照数组顺序获取
他的下一个子类没有什么干什么事情,只是将CPU的默认核心数量的两倍当成默认线程数
继续向下看NioEventLoopGroup这个实现类,对应了NioEventLoop,这个类只是重写了构造方法以及关键的newChild实现
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}
以及对EventLoop的组操作
//设置I/O比率
public void setIoRatio(int ioRatio) {
for (EventExecutor e: this) {
((NioEventLoop) e).setIoRatio(ioRatio);
}
}
//重构Selector
public void rebuildSelectors() {
for (EventExecutor e: this) {
((NioEventLoop) e).rebuildSelector();
}
}
总结
EventLoop支持提交任务和定时任务,并且直接与JAVA NIO进行交互。而EventLoopGroup则是绑定多个EventLoop,通过线性循环选择的方式拿出一个EventLoop进行操作。我们使用netty的时候会创建两个EventLoopGroup,bossEventLoop用于处理连接,work用于处理io请求,里面的每一个EventLoop都会打开一个Selector去监听事件底层事件的发生,当有事件发生的时候调用unsafe方法