https://juejin.cn/user/1451011081254792/posts
以netty的一个小demo为例(使用的源码版本为4.1.50.Final)
demo里使用到了NioEventLoopGroup
,一路跟进会调用到这个类一个比较复杂的构函数
public NioEventLoopGroup(int nThreads,
Executor executor,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads,
executor,
selectorProvider,
selectStrategyFactory,
RejectedExecutionHandlers.reject());
}
这里调用到了父类的一个构造函数
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
//DEFAULT_EVENT_LOOP_THREADS为两倍cpu核数
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
最终会调用到的MultithreadEventExecutorGroup
的一个构造函数
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
//省略
}
//executor是线程执行器
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
//nThreads等于2倍cpu核数
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
//创建NioEventLoop
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
//省略
} finally {
if (!success) {
//省略
}
}
}
//创建线程选择器
chooser = chooserFactory.newChooser(children);
//线程终止监听等其他代码,省略
}
可以看到,主要做了三件事
- 创建线程执行器
ThreadPerTaskExecutor
- 创建2倍cpu核数的NioEventLoop
- 创建线程选择器
创建ThreadPerTaskExecutor
这个类很简单
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
}
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}
可以看到,这个类就是通过传入的threadFactory
,每次执行任务时调用threadFactory创建线程执行
而这个threadFactory
是通过newDefaultThreadFactory
这个方法创建的
protected ThreadFactory newDefaultThreadFactory() {
return new DefaultThreadFactory(getClass());
}
这里会创建一个DefaultThreadFactory类,最终调用到这个类的一个构造函数
public DefaultThreadFactory(String poolName, boolean daemon, int priority, ThreadGroup threadGroup) {
ObjectUtil.checkNotNull(poolName, "poolName");
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
//省略
}
//poolName的值为nioEventLoop
prefix = poolName + '-' + poolId.incrementAndGet() + '-';
//daemon为false
this.daemon = daemon;
//priority的值为Thread.NORM_PRIORITY
this.priority = priority;
this.threadGroup = threadGroup;
}
而DefaultThreadFactory#newThread
方法如下
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;
}
所以,NioEventLoop
创建的线程的命名规则为nioEventLoop-1-xx形式
并且使用的Thread也不是原生的Thread二是FastThreadLocalThread
(主要是对ThreadLocal
做了优化)
创建NioEventLoop
上面newChild方法最终会调用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 = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
//openSelector就是调用了provider.openSelector()方法
final SelectorTuple selectorTuple = openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
}
注意到,这里
- 一个selector和一个
NioEventLoop
做唯一绑定 newTaskQueue
方法创建一个MpscQueue
,用于将一些外部任务放到NioEventLoop
中执行- 保存了
executor
,便于后续执行任务
创建线程选择器
线程选择器的选择NioEventLoop
的逻辑很简单,例如,假设有n个NioEventLoop
,则会从第1个开始选择直到第n个,然后再回到第一个继续选择
不过这样一个简单的过程netty也做了优化,
- 若线程数为2的指数幂,则创建线程选择器
PowerOfTwoEventExecutorChooser
, - 否则,创建
GenericEventExecutorChooser
这两种线程选择器的主要区别在获取下一个NioEventLoop
的方式不同
PowerOfTwoEventExecutorChooser
使用了位与操作,即在从NioEventLoop
数组中取出一个时,索引计算的方式为 (索引+1)&(数组长度-1)
GenericEventExecutorChooser
计算索引的方式则是 Math.abs((索引+1)%数组长度)
所以如果线程数是2的倍数,索引计算速度会更快