select方法的实现在SelectorImpl类中,有两种实现方式,无参的和单参的,无参方法直接调用单参方法,默认传入0:
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);
}
对传入的传参数进行判断,如果为负数则抛出异常;否则调用lockAndDoSelect,传入的参数是三目运算之后的结果:
private int lockAndDoSelect(long var1) throws IOException {
synchronized(this) {
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;
}
}
}
首先在同步锁中判断selector是否打开,如果关闭则抛出异常。isOpen方法在AbstractSelector类中实现:
private AtomicBoolean selectorOpen = new AtomicBoolean(true);
public final boolean isOpen() {
return selectorOpen.get();
}
可以看到用的是原子化的Boolean并默认为true。
回到lockAndDoSelect方法,如果selector处于开启状态,则先后以publicKeys和publicSelectedKeys为锁执行doSelect方法。
publicKeys是供外部访问的SelectionKey集合,publicSelectedKeys是供外部访问并且已经就绪的SelectionKey集合。
doSelect方法的实现在WindowsSelectorImpl类中:
protected int doSelect(long var1) throws IOException {
if (this.channelArray == null) {
throw new ClosedSelectorException();
} else {
this.timeout = var1;
this.processDeregisterQueue();
if (this.interruptTriggered) {
this.resetWakeupSocket();
return 0;
} else {
this.adjustThreadsCount();
this.finishLock.reset();
this.startLock.startThreads();
try {
this.begin();
try {
this.subSelector.poll();
} catch (IOException var7) {
this.finishLock.setException(var7);
}
if (this.threads.size() > 0) {
this.finishLock.waitForHelperThreads();
}
} finally {
this.end();
}
this.finishLock.checkForException();
this.processDeregisterQueue();
int var3 = this.updateSelectedKeys();
this.resetWakeupSocket();
return var3;
}
}
}
首先判断SelectionKeyImpl数组channelArray是否为空,如果为空则抛出异常。紧接着为timeout参数赋值;然后调用processDeregisterQueue取消准备撤销的集合。
下面是该集合和cancel(将SelectionKey加入待撤销集合)方法:
private final Set<SelectionKey> cancelledKeys = new HashSet<SelectionKey>();
void cancel(SelectionKey k) { // package-private
synchronized (cancelledKeys) {
cancelledKeys.add(k);
}
}
processDeregisterQueue方法的代码如下:
void processDeregisterQueue() throws IOException {
Set var1 = this.cancelledKeys();
synchronized(var1) {
if (!var1.isEmpty()) {
Iterator var3 = var1.iterator();
while(var3.hasNext()) {
SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();
try {
this.implDereg(var4);
} catch (SocketException var11) {
throw new IOException("Error deregistering key", var11);
} finally {
var3.remove();
}
}
}
}
}
首先获取集合并加锁,判断是否有请求取消的,若有那么就进行遍历,实际的取消操作由implDereg方法执行,最后再从集合中删除这个SelectionKeyImpl对象。
implDereg方法在WindowsSelectorImpl类中实现:
protected void implDereg(SelectionKeyImpl var1) throws IOException {
int var2 = var1.getIndex();
assert var2 >= 0;
Object var3 = this.closeLock;
synchronized(this.closeLock) {
if (var2 != this.totalChannels - 1) {
SelectionKeyImpl var4 = this.channelArray[this.totalChannels - 1];
this.channelArray[var2] = var4;
var4.setIndex(var2);
this.pollWrapper.replaceEntry(this.pollWrapper, this.totalChannels - 1, this.pollWrapper, var2);
}
var1.setIndex(-1);
}
this.channelArray[this.totalChannels - 1] = null;
--this.totalChannels;
if (this.totalChannels != 1 && this.totalChannels % 1024 == 1) {
--this.totalChannels;
--this.threadsCount;
}
this.fdMap.remove(var1);
this.keys.remove(var1);
this.selectedKeys.remove(var1);
this.deregister(var1);
SelectableChannel var7 = var1.channel();
if (!var7.isOpen() && !var7.isRegistered()) {
((SelChImpl)var7).kill();
}
}
首先获取保存在SelectionKeyImpl 中的下标,判断该下标是否是channelArray数组中的最后一个元素;如果不是最后一个,则用数组中最后一个元素覆盖到当前位置并修改其SelectionKeyImpl 对象中保存的数组下标以及pollWrapper数组中对应的描述符和事件。
无论该SelectionKeyImpl对象是否是最后一个,都将其下标置为-1,防止再次访问。然后将数组最后一个元素赋值为null,totalChannels进行自减。接下来判断是否要将轮询线程数减一(这个和增加channel时判断轮询线程数是否要加1相同,在后面进行解释)。
然后在fdMap中移除掉该SelectionKeyImpl和Channel的描述符映射(,keys和selectedKeys中同样也需要移除(keys所有注册了的SelectionKey集合,selectedKeys是所有有事件就绪的SelectionKey集合)。
接下来开始对SelectionKeyImpl对象进行注销,deregister方法在AbstractSelector类中实现:
protected final void deregister(AbstractSelectionKey key) {
((AbstractSelectableChannel)key.channel()).removeKey(key);
}
可以看到获取了channel并调用了AbstractSelectableChannel的removeKey方法:
void removeKey(SelectionKey k) { // package-private
synchronized (keyLock) {
for (int i = 0; i < keys.length; i++)
if (keys[i] == k) {
keys[i] = null;
keyCount--;
}
((AbstractSelectionKey)k).invalidate();
}
}
从keys中移除的逻辑比较简单,紧接着调用AbstractSelectionKey类中的invalidate方法:
void invalidate() { // package-private
valid = false;
}
回到implDereg,检查SelectionKeyImpl对象中保存的Channel对象的是否开启或注册了SelectionKey,如果都没有则调用kill方法,kill方法在不同的channel中实现方式不同。以SocketChannelImpl中的实现为例:
private static final int ST_UNINITIALIZED = -1;// 尚未初始化
private static final int ST_UNCONNECTED = 0;// 尚未建立连接
private static final int ST_PENDING = 1;// 未决状态
private static final int ST_CONNECTED = 2;// 连接状态
private static final int ST_KILLPENDING = 3;// KILL的未决状态
private static final int ST_KILLED = 4;// KILL状态
private int state = -1;
public void kill() throws IOException {
Object var1 = this.stateLock;
synchronized(this.stateLock) {
if (this.state != 4) {
if (this.state == -1) {
this.state = 4;
} else {
assert !this.isOpen() && !this.isRegistered();
if (this.readerThread == 0L && this.writerThread == 0L) {
nd.close(this.fd);
this.state = 4;
} else {
this.state = 3;
}
}
}
}
}
首先有六种状态,上边代码中已经注释清楚了。在同步块中首先判断是否是ST_KILLED状态,只有不是 ST_KILLED状态才有必要进行操作。如果是ST_UNINITIALIZED状态则直接改为ST_KILLED状态即可。否则断言后根据读写的线程数进行判断,如果读写操作已经全部完成则将状态变更为ST_KILLED,否则变更为ST_KILLPENDING。
接下来回到doSelect方法,在处理完待注销队列之后,判断是否触发了中断:
private volatile boolean interruptTriggered = false;
如果触发了中断,调用resetWakeupSocket方法,在调用close或者wakeup方法时interruptTriggered会被赋值为true。
先看wakeup方法:
public Selector wakeup() {
Object var1 = this.interruptLock;
synchronized(this.interruptLock) {
if (!this.interruptTriggered) {
this.setWakeupSocket();
this.interruptTriggered = true;
}
return this;
}
}
如果当前没有触发中断,则调用setWakeupSocket方法之后interruptTriggered 赋值为true。
setWakeupSocket方法:
private void setWakeupSocket() {
this.setWakeupSocket0(this.wakeupSinkFd);
}
private native void setWakeupSocket0(int var1);
在selector创建时生成过两个SocketChannel,一个SourceChannelImpl和一个SinkChannelImpl,wakeupSinkFd就是SinkChannelImpl的句柄。
setWakeupSocket0的实现:
Java_sun_nio_ch_WindowsSelectorImpl_setWakeupSocket0(JNIEnv *env, jclass this,
jint scoutFd) {
/* Write one byte into the pipe */
const char byte = 1;
send(scoutFd, &byte, 1, 0);
}
通过这个双向通道的sink端向source发送一个字节的数据,这样source端描述符就进入就绪状态,就能被select感知到,Selector便被唤醒。
下来看看AbstractSelector中的close方法:
private AtomicBoolean selectorOpen = new AtomicBoolean(true);
public final void close() throws IOException {
boolean open = selectorOpen.getAndSet(false);
if (!open)
return;
implCloseSelector();
}
selectorOpen 是原子化的Boolean类型,并且在初始化时默认为true。首先调用AtomicBoolean中的getAndSet方法将selectorOpen的值设为false:
public final boolean getAndSet(boolean newValue) {
boolean prev;
do {
prev = get();
} while (!compareAndSet(prev, newValue));
return prev;
}
回到close方法如果之前是关闭状态,则直接return;否则调用SelectorImpl中实现的implCloseSelector方法:
public void implCloseSelector() throws IOException {
this.wakeup();
synchronized(this) {
Set var2 = this.publicKeys;
synchronized(this.publicKeys) {
Set var3 = this.publicSelectedKeys;
synchronized(this.publicSelectedKeys) {
this.implClose();
}
}
}
}
首先调用wakeup方法,接着调用implClose方法,这两个方法的实现都在WindowsSelectorImpl类中:
protected void implClose() throws IOException {
Object var1 = this.closeLock;
synchronized(this.closeLock) {
if (this.channelArray != null && this.pollWrapper != null) {
Object var2 = this.interruptLock;
synchronized(this.interruptLock) {
this.interruptTriggered = true;
}
this.wakeupPipe.sink().close();
this.wakeupPipe.source().close();
for(int var7 = 1; var7 < this.totalChannels; ++var7) {
if (var7 % 1024 != 0) {
this.deregister(this.channelArray[var7]);
SelectableChannel var3 = this.channelArray[var7].channel();
if (!var3.isOpen() && !var3.isRegistered()) {
((SelChImpl)var3).kill();
}
}
}
this.pollWrapper.free();
this.pollWrapper = null;
this.selectedKeys = null;
this.channelArray = null;
Iterator var8 = this.threads.iterator();
while(var8.hasNext()) {
WindowsSelectorImpl.SelectThread var9 = (WindowsSelectorImpl.SelectThread)var8.next();
var9.makeZombie();
}
this.startLock.startThreads();
}
}
}
在同步块中首先通过channelArray 和pollWrapper的状态判断是否有需要关闭的资源,如果有需要关闭的资源,首先在同步块中设置中断为true,然后关闭创建selector时打开的两个channel,然后在循环中关闭不活跃的channel。接下来是对SelectThread的处理,放在后面进行分析。
下面回到doSelect方法,如果已经触发了中断,调用resetWakeupSocket方法:
private void resetWakeupSocket() {
Object var1 = this.interruptLock;
synchronized(this.interruptLock) {
if (this.interruptTriggered) {
this.resetWakeupSocket0(this.wakeupSourceFd);
this.interruptTriggered = false;
}
}
}
private native void resetWakeupSocket0(int var1);
resetWakeupSocket0也是一个native方法,和setWakeupSocket0正好互补,用来读取setWakeupSocket0中发送的数据,再将interruptTriggered设置为false,最后doSelect将会立即返回0,而不会调用poll操作。
如果doSelect方法中interruptTriggered为false,则先调用adjustThreadsCount方法调整轮询线程的数量,:
private final List<WindowsSelectorImpl.SelectThread> threads = new ArrayList();
private void adjustThreadsCount() {
int var1;
if (this.threadsCount > this.threads.size()) {
for(var1 = this.threads.size(); var1 < this.threadsCount; ++var1) {
WindowsSelectorImpl.SelectThread var2 = new WindowsSelectorImpl.SelectThread(var1);
this.threads.add(var2);
var2.setDaemon(true);
var2.start();
}
} else if (this.threadsCount < this.threads.size()) {
for(var1 = this.threads.size() - 1; var1 >= this.threadsCount; --var1) {
((WindowsSelectorImpl.SelectThread)this.threads.remove(var1)).makeZombie();
}
}
}
SelectThread类是WindowsSelectorImpl类的内部类:
private final class SelectThread extends Thread {
private final int index;
final WindowsSelectorImpl.SubSelector subSelector;
private long lastRun;
private volatile boolean zombie;
private SelectThread(int var2) {
this.lastRun = 0L;
this.index = var2;
this.subSelector = WindowsSelectorImpl.this.new SubSelector(var2);
this.lastRun = WindowsSelectorImpl.this.startLock.runsCounter;
}
void makeZombie() {
this.zombie = true;
}
boolean isZombie() {
return this.zombie;
}
public void run() {
for(; !WindowsSelectorImpl.this.startLock.waitForStart(this); WindowsSelectorImpl.this.finishLock.threadFinished()) {
try {
this.subSelector.poll(this.index);
} catch (IOException var2) {
WindowsSelectorImpl.this.finishLock.setException(var2);
}
}
}
}
在channel注册时channel的总数每达到1024的倍数,轮询线程数会加一。index是当前SelectThread在ArrayList threads中的下标,lastRun在后边用到的时候再说,zombie用来表示状态;
如果channel的总数小于1024,threads为0,轮询由WindowsSelectorImpl来执行:
private final WindowsSelectorImpl.SubSelector subSelector = new WindowsSelectorImpl.SubSelector();
如果channel的总数大于1024,则需要WindowsSelectorImpl和SelectThread对象开启新线程一起执行轮询。
SubSelector 也是一个内部类(只复制一些在本文中用的到的代码):
private final class SubSelector {
private final int pollArrayIndex;
private final int[] readFds;
private final int[] writeFds;
private final int[] exceptFds;
private SubSelector() {
this.readFds = new int[1025];
this.writeFds = new int[1025];
this.exceptFds = new int[1025];
this.pollArrayIndex = 0;
}
private SubSelector(int var2) {
this.readFds = new int[1025];
this.writeFds = new int[1025];
this.exceptFds = new int[1025];
this.pollArrayIndex = (var2 + 1) * 1024;
}
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);
}
private int poll(int var1) throws IOException {
return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress + (long)(this.pollArrayIndex * PollArrayWrapper.SIZE_POLLFD), Math.min(1024, WindowsSelectorImpl.this.totalChannels - (var1 + 1) * 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
}
private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);
...
}
一共给出了两种构造方法,区别在于对pollArrayIndex成员的赋值。poll方法的单参和无参两种实现方式都调用了poll0方法,poll0方法的第一个参数是轮询时在内存中的起始位置,第二个参数是线程应负责轮询的channel的总数。
第三到五个参数传入的:readFds 、writeFds、exceptFds 分别对应读、写、异常描述符 ,在SubSelector构造中初始化大小都是1025,多出来的一个就是前面说过的wakeupSourceFd描述符,用于唤醒,所以是1025。
最后一个参数是超时时间,在调用select单参方法时传入。
除此之外还有两个内部类StartLock和FinishLock:
private final WindowsSelectorImpl.StartLock startLock = new WindowsSelectorImpl.StartLock();
private final WindowsSelectorImpl.FinishLock finishLock = new WindowsSelectorImpl.FinishLock();
private final class StartLock {
private long runsCounter;
private StartLock() {
}
private synchronized void startThreads() {
++this.runsCounter;
this.notifyAll();
}
private synchronized boolean waitForStart(WindowsSelectorImpl.SelectThread var1) {
while(this.runsCounter == var1.lastRun) {
try {
WindowsSelectorImpl.this.startLock.wait();
} catch (InterruptedException var3) {
Thread.currentThread().interrupt();
}
}
if (var1.isZombie()) {
return true;
} else {
var1.lastRun = this.runsCounter;
return false;
}
}
}
private final class FinishLock {
private int threadsToFinish;
IOException exception;
private FinishLock() {
this.exception = null;
}
private void reset() {
this.threadsToFinish = WindowsSelectorImpl.this.threads.size();
}
private synchronized void threadFinished() {
if (this.threadsToFinish == WindowsSelectorImpl.this.threads.size()) {
WindowsSelectorImpl.this.wakeup();
}
--this.threadsToFinish;
if (this.threadsToFinish == 0) {
this.notify();
}
}
private synchronized void waitForHelperThreads() {
if (this.threadsToFinish == WindowsSelectorImpl.this.threads.size()) {
WindowsSelectorImpl.this.wakeup();
}
while(this.threadsToFinish != 0) {
try {
WindowsSelectorImpl.this.finishLock.wait();
} catch (InterruptedException var2) {
Thread.currentThread().interrupt();
}
}
}
private synchronized void setException(IOException var1) {
this.exception = var1;
}
private void checkForException() throws IOException {
if (this.exception != null) {
StringBuffer var1 = new StringBuffer("An exception occurred during the execution of select(): \n");
var1.append(this.exception);
var1.append('\n');
this.exception = null;
throw new IOException(var1.toString());
}
}
}
startLock和finishLock都只有一份。
结合上边所提到的内部类继续看doSelect方法的代码,在调用adjustThreadsCount调整了线程数量之后,调用了finishLock的reset方法:
private void reset() {
this.threadsToFinish = WindowsSelectorImpl.this.threads.size();
}
将threadsToFinish赋值为threads的总数,接着调用startLock的startThreads方法:
private synchronized void startThreads() {
++this.runsCounter;
this.notifyAll();
}
对runsCounter进行自增并唤醒所有的阻塞。
接下来调用AbstractSelector类中实现的begin方法:
protected final void begin() {
if (interruptor == null) {
interruptor = new Interruptible() {
public void interrupt(Thread ignore) {
AbstractSelector.this.wakeup();
}};
}
AbstractInterruptibleChannel.blockedOn(interruptor);
Thread me = Thread.currentThread();
if (me.isInterrupted())
interruptor.interrupt(me);
}
若是中断器interruptor=null,就创建一个,当当前线程阻塞在I/O操作上并且发生了线程级别的中断时,就会调用wakeup方法唤醒Selector。
begin方法结束后先使WindowsSelectorImpl执行轮询,直接调用subSelector.poll();
接着判断链表threads的长度是否大于零,如果大于零则还需要调用.finishLock.waitForHelperThreads方法(上边说了要开启新的线程执行轮询操作):
private synchronized void waitForHelperThreads() {
if (this.threadsToFinish == WindowsSelectorImpl.this.threads.size()) {
WindowsSelectorImpl.this.wakeup();
}
while(this.threadsToFinish != 0) {
try {
WindowsSelectorImpl.this.finishLock.wait();
} catch (InterruptedException var2) {
Thread.currentThread().interrupt();
}
}
}
this.threadsToFinish == WindowsSelectorImpl.this.threads.size()的判断是为帮助唤醒所有处于poll阻塞的轮询。结合SelectThread类中的run方法,这里的逻辑就清晰易懂了。最后调用end方法:
protected final void end() {
AbstractInterruptibleChannel.blockedOn(null);
}
源码中对此方法的注释是:标记可能无限期阻塞的I / O操作的结束。
接下来是调用FinishLock中的checkForException方法对异常进行检查。之后调用processDeregisterQueue来取消可能在select轮询时发生的SelectionKeyl的撤销。这个方法之间已经说过了。
接下来调用updateSelectedKeys更新SelectedKey:
private long updateCount = 0L;
private int updateSelectedKeys() {
++this.updateCount;
byte var1 = 0;
int var4 = var1 + this.subSelector.processSelectedKeys(this.updateCount);
WindowsSelectorImpl.SelectThread var3;
for(Iterator var2 = this.threads.iterator(); var2.hasNext(); var4 += var3.subSelector.processSelectedKeys(this.updateCount)) {
var3 = (WindowsSelectorImpl.SelectThread)var2.next();
}
return var4;
}
首先对updateCount进行自增,然后调用subSelector的processSelectedKeys方法,得到poll返回的就绪的Channel描述符,也就是得到事件就绪的Channel个数,同理也就需要得到所有SelectThread中的。
其中processSelectedKeys方法如下:
private int processSelectedKeys(long var1) {
byte var3 = 0;
int var4 = var3 + this.processFDSet(var1, this.readFds, Net.POLLIN, false);
var4 += this.processFDSet(var1, this.writeFds, Net.POLLCONN | Net.POLLOUT, false);
var4 += this.processFDSet(var1, this.exceptFds, Net.POLLIN | Net.POLLCONN | Net.POLLOUT, true);
return var4;
}
调用processFDSet方法处理了读写以及异常:
private int processFDSet(long var1, int[] var3, int var4, boolean var5) {
int var6 = 0;
for(int var7 = 1; var7 <= var3[0]; ++var7) {
int var8 = var3[var7];
if (var8 == WindowsSelectorImpl.this.wakeupSourceFd) {
synchronized(WindowsSelectorImpl.this.interruptLock) {
WindowsSelectorImpl.this.interruptTriggered = true;
}
} else {
WindowsSelectorImpl.MapEntry var9 = WindowsSelectorImpl.this.fdMap.get(var8);
if (var9 != null) {
SelectionKeyImpl var10 = var9.ski;
if (!var5 || !(var10.channel() instanceof SocketChannelImpl) || !WindowsSelectorImpl.this.discardUrgentData(var8)) {
if (WindowsSelectorImpl.this.selectedKeys.contains(var10)) {
if (var9.clearedCount != var1) {
if (var10.channel.translateAndSetReadyOps(var4, var10) && var9.updateCount != var1) {
var9.updateCount = var1;
++var6;
}
} else if (var10.channel.translateAndUpdateReadyOps(var4, var10) && var9.updateCount != var1) {
var9.updateCount = var1;
++var6;
}
var9.clearedCount = var1;
} else {
if (var9.clearedCount != var1) {
var10.channel.translateAndSetReadyOps(var4, var10);
if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
WindowsSelectorImpl.this.selectedKeys.add(var10);
var9.updateCount = var1;
++var6;
}
} else {
var10.channel.translateAndUpdateReadyOps(var4, var10);
if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
WindowsSelectorImpl.this.selectedKeys.add(var10);
var9.updateCount = var1;
++var6;
}
}
var9.clearedCount = var1;
}
}
}
}
}
return var6;
}
这个方法其实就是把poll0方法轮询的描述符结果放入传入的数组中,然后通过遍历这个数组,得到相应的Channel描述符,因为之前通过fdMap保存了Channel的描述符和SelectionKeyImpl的映射关系,那么就可以根据Channel描述符找到对应的SelectionKeyImpl对象,再根据传入的状态值var4来更新Channel的状态,最后将其保存在selectedKeys集合中供外部访问。
下面是网上找到的一张图,结合图片更容易理解jdk中NIO的架构。
对jdk中NIO源码的学习到此告一段落。