前面已经介绍过了Selector的open函数以及channel的register函数,现在分析最后一个函数:select()函数。
selector.select()在Selector类中此方法是一个抽象的。如下:
public abstract int select() throws IOException;
函数功能:选择一些I/O操作已经准备好的channel。每个channel对应着一个key。这个方法是一个阻塞的选择操作。当至少有一个通道被选择时才返回。当这个方法被执行时,当前线程是允许被中断的。
除了这个方法之外,还有两个重载方法:
1. public abstract int select(long timeout)throws IOException;
2. public abstract int selectNow() throws IOException;
这里的timeout说明如下:
- 如果 timeout为正,则select(long timeout)在等待有通道被选择时至多会阻塞timeout毫秒
- 如果timeout为零,则永远阻塞直到有至少一个通道准备就绪。
- timeout不能为负数
首先我们来看一下这三个方法的实现:是在SelectorImpl这个类里面:
public int select(long var1) throws IOException {
if(var1 < 0L) {
throw new IllegalArgumentException("Negative timeout");
} else {
return this.lockAndDoSelect(var1 == 0L?-1L:var1);
}
}
public int select() throws IOException {
return this.select(0L);
}
public int selectNow() throws IOException {
return this.lockAndDoSelect(0L);
}
我们可以发现,这三个方法最终都是调用了
lockAndDoSelect(0L);
这个函数,这个函数也是在SelectorImpl这个类里面实现的,源码如下:
private int lockAndDoSelect(long var1) throws IOException {
synchronized(this) {
//坚持selector是否已经打开了
if(!this.isOpen()) {
throw new ClosedSelectorException();
} else {
Set var4 = this.publicKeys;
int var10000;
//这里用了双重锁来实现同步访问,双重锁可能引起死锁。
synchronized(this.publicKeys) {
Set var5 = this.publicSelectedKeys;
synchronized(this.publicSelectedKeys) {
var10000 = this.doSelect(var1);
}
}
return var10000;
}
}
}
这里先看下这个isOpen()函数的具体实现,这个函数是在 AbstractSelector 中实现的。
public final boolean isOpen() {
return selectorOpen.get();
}
函数的功能:检查这个Selector是否打开;
在isOpen()方法中的selectorOpen变量在AbstractSelector类中定义如下:
private AtomicBoolean selectorOpen = new AtomicBoolean(true);
即selectorOpen是一个原子性的变量;如果在执行selector.select()方法之前执行了Selector selector = Selector.open();则selectorOpen就进行了初始化,为true。否则为false。
最后分析一下lockAndDoSelect()中的核心函数:
doSelect(var1);
传参数是var1是一个long型整数,表示阻塞等待的时间。
doselect()方法也是一个abstract方法。他有两个实现:
由于我是在Mac下,所以默认调用的是KQueueSelectorImpl这个类里面的实现,源码如下:
protected int doSelect(long var1) throws IOException {
boolean var3 = false;
//判断当前selector是否是关闭的,
if(this.closed) {
throw new ClosedSelectorException();
} else {
this.processDeregisterQueue();
int var7;
try {
this.begin();
var7 = this.kqueueWrapper.poll(var1);
} finally {
this.end();
}
this.processDeregisterQueue();
return this.updateSelectedKeys(var7);
}
}
这部分代码还是比较复杂的,这里我只分析核心的地方:
var7 = this.kqueueWrapper.poll(var1);
这个函数的内部会调用系统的poll函数,轮询kqueueWrapper里面保存的fd,内部会调用native方法。这里会监听fd中是否有数据进出,这回造成IO阻塞,直到有数据读写事件发生。