1. 我们的问题
netty在创建selector的时候就尝试了优化,具体优化其实是将底层的数据结构从HashSet改为了数组,可以从SelectedSelectionKeySet和SelectorImpl的源码看到这一点,这里就不列了。
记住有个优化就行了,下面会用到这个知识
但是由此引发一个问题,当开启优化后,那么有效连接集合的数据是从哪来的?
当IO事件发生后,会紧接着调用processSelectedKeys()来处理具体的IO消息:
public final class NioEventLoop extends SingleThreadEventLoop {
private void processSelectedKeys() {
if (selectedKeys != null) {
//开启优化
processSelectedKeysOptimized();
} else {
//未开启优化
processSelectedKeysPlain(selector.selectedKeys());
}
}
默认开启优化,走到该分支processSelectedKeysOptimized(),问题来了,该方法内部直接使用成员变量selectedKeys,那么这个集合的数据是怎么来的呢?我们没有调用类似else分支里面的selector.selectedKeys()
啊
2. 问题分析
问题很简单,但是不太容易直接想到。
原因就是优化引起差别的,我们看下具体是怎么优化的:
private SelectorTuple openSelector() {
final Selector unwrappedSelector;
try {
//[1] 根据底层的IO模型来创建一个selector,这里的selector就是java中NIO的selector
unwrappedSelector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
// [2]如果未开启优化则直接就返回了,SelectorTuple可以视为一个持有selector引用的句柄
if (DISABLE_KEY_SET_OPTIMIZATION) {
return new SelectorTuple(unwrappedSelector);
}
//[3] 开启优化参数,则进行优化
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
// 通过反射创建一个selector的具体实例
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
// [4]netty自己包装的一个selectKey的集合类,回被用来替换selector原生的selectedKeys成员变量
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {//反射,获取selectedKeys字段,后面会替换这个私有的成员变量
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
// java 9 以上版本会用Unsafe类直接底层替换SelectionKeySet
}
// 利用反射将原生selector中的两个属性替换为netty自己的包装类
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
} catch (NoSuchFieldException e) {
return e;
} catch (IllegalAccessException e) {
return e;
}
}
});
//[5] 当前NioEventLoop就持有这个自定义的集合
selectedKeys = selectedKeySet;
return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}
- 【1】 根据底层的IO模型来创建一个selector,这里的selector就是java中NIO的selector
- 【2】如果未开启优化则直接就返回了,SelectorTuple可以视为一个持有selector引用的句柄
- 【3】 开启优化参数,则进行优化。默认是开启优化的。
- 【4】netty自己包装的一个selectKey的集合类,回被用来替换selector原生的selectedKeys成员变量
- 【5】当前NioEventLoop就持有这个自定义的集合。
优化的核心内容就是用自定义的集合替换有效key的集合,优点是效率高。
我们看下原生的语法:
原生的语法就是未开启优化processSelectedKeysPlain(selector.selectedKeys());
分支入参中的语法,而当开启优化后,当前NioEventLoop就持有这个自定义的集合了。当IO事件发生后,就绪的channels和keys就会放入selector内,我们知道java中是对象引用,因此,我们直接用这个被自己持有的集合就行了。