Selector源码学习笔记
Selector学习笔记.
Selector open流程
Selector.open()
可以看到Selector是由Selector.provider()提供的provider提供的.所以接下来看看SelectorProvider::provider方法
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
SelectorProvider.provider()
provider返回有多种情况,具体优先级见下
- 从系统环境变量"java.nio.channels.spi.SelectorProvider"中加载指定的Provider
- 通过java SPI加载Provider实现
- 默认Provider,通过sun.nio.ch.DefaultSelectorProvider.create()创建,且根据jdk不同rt包里的实现也不同.
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
//从系统环境变量中"java.nio.channels.spi.SelectorProvider"加载Provider实现.
if (loadProviderFromProperty())
return provider;
//通过JAVA SPI加载Provider的实现
if (loadProviderAsService())
return provider;
//默认Provider
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
sun.nio.ch.DefaultSelectorProvider.create()
DefaultSelectorProvider在不同的rt包实现不一样.所以通过这种方式会因为不同的操作系统得到不一样的结果.
截止jdk8为止
可获取的具体Selector实现如下:
windows版DefaultSelectorProvider
windows版本就直接返回windowsSelectorProvider
public static SelectorProvider create() {
return new WindowsSelectorProvider();
}
linux版DefaultSelectorProvider
linux版本则根据环境变量os.name返回对应的provider.
2.6以后的linux返回的是EPollSelectorProvider
public static SelectorProvider create() {
String var0 = (String)AccessController.doPrivileged(new GetPropertyAction("os.name"));
if (var0.equals("SunOS")) {
return createProvider("sun.nio.ch.DevPollSelectorProvider");
} else {
return (SelectorProvider)(var0.equals("Linux") ? createProvider("sun.nio.ch.EPollSelectorProvider") : new PollSelectorProvider());
}
}
SelectorProvider.openSelector()
不同的provider来自于SelectorProvider.provider()创建,而具体provider的openSelector方法则负责返回具体的Selector.
WindowsSelectorProvider.openSelector()
返回WindowsSelectorImpl.java
public AbstractSelector openSelector() throws IOException {
return new WindowsSelectorImpl(this);
}
EPollSelectorProvider.openSelector()
返回EPollSelectorImpl
public AbstractSelector openSelector() throws IOException {
return new EPollSelectorImpl(this);
}
register流程
AbstractSelectableChannel::register
如上图所示,首先只有SelectableChannel可以注册至selector上,而我们常用到的大部分channel基本都继承自SelectableChannel.
接下来看看AbstractSelectableChannel::register的API介绍
使用给定的Selector注册此Channel,并返回SelectionKey。
如果此Channel已在给定的Selector中注册,则将其兴趣(ops参数)设置为给定值后,返回表示注册的SelectionKey.
若此Channel尚未在给定的Selector中注册,则在保持适当的锁的同时调用AbstractSelector::register方法进行注册.返回key之前先添加进channel的key集合中.(每个channel也维护了自己注册过的selector集合)
public final SelectionKey register(Selector sel, int ops,
Object att) throws ClosedChannelException
{
synchronized (regLock) {
//验证通道是否打开
if (!isOpen())
throw new ClosedChannelException();
//validOps返回子类支持的ops,即对应的位数值为1. 此处校验ops是否有支持权限外的位数值为1
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
//selector不支持阻塞channel注册.此处检查了channel是否为阻塞模式.
if (blocking)
throw new IllegalBlockingModeException();
//检查此channel是否已经注册过Selector了.
SelectionKey k = findKey(sel);
//如果注册过,就更新ops和attachment
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
//如果没有注册过
if (k == null) {
// New registration
synchronized (keyLock) {
//检查连接是否是打开的
if (!isOpen())
throw new ClosedChannelException();
//走selector的register方法注册此channel.
k = ((AbstractSelector)sel).register(this, ops, att);
//并添加至此channel记录的注册的Selector集合.
addKey(k);
}
}
return k;
}
}
看下来,发现channel最后还是通过调用Selector::register方法进行注册,所以接下来看Selector的register方法.
SelectorImpl::register
SelectorImpl实现了Selector的register接口.SelectionKey的创建是在这一步进行的.
这一步又会调用具体Selector实现(如WindowsSelectorImpl、EPollSelectorImpl等)的implRegister方法.
protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
//只允许SelChImpl实现类可以注册.
if (!(var1 instanceof SelChImpl)) {
throw new IllegalSelectorException();
} else {
//创建SelectionKey并返回.
SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
var4.attach(var3);
synchronized(this.publicKeys) {
//注册到Selector实现里.
this.implRegister(var4);
}
var4.interestOps(var2);
return var4;
}
}
WindowsSelectorImpl::implRegister
windows下的JDK8默认实现是这个. 总的来说selector维护了一批SelectionKey.
//从SelectorImpl下继承来的.
protected HashSet<SelectionKey> keys = new HashSet();
//一个存放 selectionKey的Map(继承于HashMap)
private final WindowsSelectorImpl.FdMap fdMap = new
WindowsSelectorImpl.FdMap();
//一个用于存放SelectionKey的数组. 此处自己用数组实现增删可能是为了快?
private SelectionKeyImpl[] channelArray = new
SelectionKeyImpl[8];
private int totalChannels = 1;
protected void implRegister(SelectionKeyImpl var1) {
synchronized(this.closeLock) {
if (this.pollWrapper == null) {
throw new ClosedSelectorException();
} else {
//判断数组是否需要扩容
this.growIfNeeded();
//追加至channelArray数组的末尾.
this.channelArray[this.totalChannels] = var1;
//并且selectionKey自己也记录着自己在selector中的哪个位置.
var1.setIndex(this.totalChannels);
//往map集合里存一个
this.fdMap.put(var1);
//往set集合里存一个
this.keys.add(var1);
//存放文件句柄信息( fd ( file description ))
this.pollWrapper.addEntry(this.totalChannels, var1);
++this.totalChannels;
}
}
}
EpollSelectorImpl::implRegister
protected void implRegister(SelectionKeyImpl key) {
if (this.closed) {
throw new ClosedSelectorException();
} else {
SelChImpl ch = key.channel;
//文件描述符
int fd = Integer.valueOf(ch.getFDVal());
//记录fd与selectoinKey的映射.
this.fdToKey.put(fd, key);
//将新的fd存入EPollArrayWrapper(负责对Epoll调用)
this.pollWrapper.add(fd);
//往set集合里存一个
this.keys.add(key);
}
}
select流程
SelectorImpl.doSelect(long timeout)
WindowsSelectorImpl::doSelect
windows JDK8 默认自带的Selector的doSelect()方法.
//存放着文件句柄,基本用unsafe直接操作内存.
private PollArrayWrapper pollWrapper = new PollArrayWrapper(8);
protected int doSelect(long var1) throws IOException {
if (this.channelArray == null) {
throw new ClosedSelectorException();
} else {
this.timeout = var1;
//清除注销队列(有个集合记录了cancel的key.通过这个方法将其正式清除)
this.processDeregisterQueue();
if (this.interruptTriggered) {
this.resetWakeupSocket();
return 0;
} else {
//启动/销毁WindowsSelectorImpl.SelectThread线程,保证有threadsCount(每增加1024个连接,这个值+1)个线程在工作.每个SelectThread都有一个SubSelector去poll这个Selector里的PollArrayWrapper里的句柄. *推测* 并发分段轮询机制.
this.adjustThreadsCount();
this.finishLock.reset();
this.startLock.startThreads();
try {
this.begin();
try {
//负责一开始1024个channel的poll工作.后面的每1024个连接都将给SelectThread的SubSelector负责poll. 据说系统调用的poll会因为句柄太多影响性能,所以进行拆分.(待证)
this.subSelector.poll();
} catch (IOException var7) {
this.finishLock.setException(var7);
}
if (this.threads.size() > 0) {
this.finishLock.waitForHelperThreads();
}
} finally {
this.end();
}
this.finishLock.checkForException();
//清除Cancel了的连接.
this.processDeregisterQueue();
//更新keys事件. 这个方法的核心在于SubSelector的processSelectedKeys()方法
int var3 = this.updateSelectedKeys();
this.resetWakeupSocket();
return var3;
}
}
}
EPollSelectorImpl::doSelect
protected int doSelect(long timeout) throws IOException {
if (this.closed) {
throw new ClosedSelectorException();
} else {
this.processDeregisterQueue();
try {
this.begin();
this.pollWrapper.poll(timeout);
} finally {
this.end();
}
this.processDeregisterQueue();
int var3 = this.updateSelectedKeys();
if (this.pollWrapper.interrupted()) {
this.pollWrapper.putEventOps(this.pollWrapper.interruptedIndex(), 0);
synchronized(this.interruptLock) {
this.pollWrapper.clearInterrupted();
IOUtil.drain(this.fd0);
this.interruptTriggered = false;
}
}
return var3;
}
}
poll具体实现
WindowsSelectorImpl.SelectThread.SubSelector::poll
此处windows每次poll系统调用传递一堆fd,推测此处是比linux慢的原因?
private SubSelector(int var2) {
this.readFds = new int[1025];
this.writeFds = new int[1025];
this.exceptFds = new int[1025];
this.pollArrayIndex = (var2 + 1) * 1024;
}
//每次poll都传递一堆的fd
private int poll() throws IOException {
return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
}
EPollArrayWrapper::poll
int poll(long var1) throws IOException {
this.updateRegistrations();
// native方法,没进去看.但估计基本上就是调用epoll的wait了.
this.updated = this.epollWait(this.pollArrayAddress, NUM_EPOLLEVENTS, var1, this.epfd);
for(int var3 = 0; var3 < this.updated; ++var3) {
if (this.getDescriptor(var3) == this.incomingInterruptFD) {
this.interruptedIndex = var3;
this.interrupted = true;
break;
}
}
return this.updated;
}