先来段netty服务器端代码:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup(4);
try
{
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup, workGroup);
server.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100);
server.childHandler(new DealNettyServerInitializer());
ChannelFuture future = server.bind(7878).sync();
future.channel().closeFuture().sync();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
finally
{
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
代码的一开始:EventLoopGroup类,是什么东东?一起来看下
当我们没有传入参数时,默认调用的是参数为0的构造方法,这个参数是个什么意思,看下带有参数的构造方法。
现在我们应该知道了 number of threads。我们传入了0,就不创建线程了吗?继续跟进去看看。
哈,这有个判断,如果传入的nThreads为0,就是默认值。这个默认是多少呢,瞅一眼。。。
应该是NettyRuntime.availableProcessors()的两倍,那NettyRuntime.availableProcessors()是多少呢?
哦,原来是CPU的核数呀。。因此证明,当运算是IO密集型时候,建议设置CPU核数的2倍呀。
继续回到
这个方法上,跟进父类的构造方法。
记一下DefaultEventExecutorChooserFactory.INSTANCE这个参数,等下我们看个非常有意思的东西。
/**
* Create a new instance.
*
* @param nThreads the number of threads that will be used by this instance.
* @param executor the Executor to use, or {@code null} if the default should be used.
* @param chooserFactory the {@link EventExecutorChooserFactory} to use.
* @param args arguments which will passed to each {@link #newChild(Executor, Object...)} call
*/
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
就到了真正的逻辑部分了。代码有点多,分为三个部分:
1、new ThreadPerTaskExecutor(newDefaultThreadFactory());线程执行器的实例化
2、循环构建NioEventLoop实例
3、chooserFactory.newChooser(children);线程选择器。
来,一点一点的分析。
1、new ThreadPerTaskExecutor(newDefaultThreadFactory());线程执行器的实例化
因为刚开始传入的是null,所以这里会新创建一个ThreadPerTaskExecutor对象,并调用newDefaultThreadFactory()方法作为参数。我们一看,这个方法的名字是xxThreadFactory,猜测可能是和线程池类似,是一个默认的线程工厂类。进入看一下
创建了一个DefaultThreadFactory类,并将该类的class作为参数传入。
看下DefaultThreadFactory类的构造方法。。
看下toPoolName()方法,
该方法就是将class io.netty.channel.nio.NioEventLoopGroup名字转成nioEventLoopGroup,继续跟进
我们看到了prefix为nioEventLoopGroup-1-;即为netty使用线程名的前缀。
再瞅下ThreadPerTaskExecutor这个线程执行器类,
此类就一个方法,execute明显是供外界调用的,看下那个线程工厂的newThread方法。。
该方法是创建了一个FastThreadLocalThread的类,这应该是一个线程类,和Thread应该是一样的,线程的名字为prefix+1即为prefix为nioEventLoopGroup-1-1。
果然,该类就是线程类。
总结下,executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());这个代码:
首先创建一个执行线程执行器类,并将默认的线程工厂类注入进去。当线程执行器执行execute方法时,线程工厂就会新建一个FastThreadLocalThread线程类,名字为nioEventLoopGroup-1-x。
2、循环构建NioEventLoop实例
回到
这个代码上,继续向下看:
循环为children数组实例化对象。即children[i] = newChild(executor, args);
实例化的对象为NioEventLoop。PS:突然发现,NioEventLoopGroup是由多个NioEventLoop组成。。。这名子取得真是NB。
瞅下NioEventLoop这个类:
进入父类的构造方法:
继续查看父类的构造方法:
看下taskQueue=newTask...方法:
该newTaskQueue覆盖了父类的
关于newMpscQueue和LinkedBlockingQueue的比较,请看https://my.oschina.net/hmilyylimh/blog/1787788,这个大牛分析的还是比较透彻的。
所以此处的任务队列是netty做了一次优化。
回到
这个方法,看完父类的构造方法,看下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);
}
if (DISABLE_KEYSET_OPTIMIZATION) {
return new SelectorTuple(unwrappedSelector);
}
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
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;
}
}
});
if (!(maybeSelectorImplClass instanceof Class) ||
// ensure the current selector implementation is what we can instrument.
!((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
if (maybeSelectorImplClass instanceof Throwable) {
Throwable t = (Throwable) maybeSelectorImplClass;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
}
return new SelectorTuple(unwrappedSelector);
}
final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
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;
}
}
});
if (maybeException instanceof Exception) {
selectedKeys = null;
Exception e = (Exception) maybeException;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
return new SelectorTuple(unwrappedSelector);
}
selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectorTuple(unwrappedSelector,
new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}
卧槽,这么多,不就是为NioEvenetLoop对象创建一个selector对象么: unwrappedSelector = provider.openSelector();为啥会有这么多内容呢???
耐心往下看一点,
嗯?这是什么鬼?
哦,原来实现了AbstractSet接口,用数组取代HashSet呀。
继续往下看:
对sun.nio.ch.SelectorImpl这个类进行反射,获取该类的Class对象。
然后通过反射,将SelectorImpl对象中的selectedKeys和publicSelectedKeys设值为SelectedSelectionKeySet对象。debug下:
每调用反射前:
调用反射后:
这个也是netty对selectKey做到一个优化点。
至此,完成了对NioEventLoop的select的绑定。
总结下:
NioEventLoop对象实例化时候,先是绑定线程执行器,然后为NioEventLoop对象创建一个性能比较高的mpsc队列,然后再优化select选择器的selectKeys并绑定到该NioEventLoop对象中。
3、chooserFactory.newChooser(children);线程选择器。
回到:
中,继续向下看:
还记的前面记的DefaultEventExecutorChooserFactory.INSTANCE这个参数么,现在就用到了。
这个就是从NioEventLoop数组中轮询选取一个NioEventLoop对象。
看下这个newChooser方法:
看下isPowerOfTwo:
该方法是判断val是否为2的指数倍,即2,4,8,16。。。
好高级的写法呀。
所以如果是2的指数倍,采用PowerOfTwoEventExecutorChooser否则GenericEventExecutorChooser。
看下PowerOfTwoEventExecutorChooser
用&的方式选择下一个值。
看下GenericEventExecutorChooser
则是通过取余的方式选取下一个值。
就这么一点,netty也是做了优化,可见netty是有多么优秀。
至此NioEventLoopGroup的实例化分析完毕。。。