WindowsSelectorImpl是Java nio 基于windows操作系统的基础类,这篇文章主要讲解两个方法:doSelect(long var1)和implRegister(SelectionKeyImpl var1)
private final int INIT_CAP = 8;
//每个线程处理channel的数量
private static final int MAX_SELECTABLE_FDS = 1024;
//注册的selectionKey数组
private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[8];
//底层的本机轮询数组包装对象,用于存放Socket文件描述符和事件掩码
private PollArrayWrapper pollWrapper = new PollArrayWrapper(8);
private int totalChannels = 1;
private int threadsCount = 0;
//辅助线程数组
private final List<WindowsSelectorImpl.SelectThread> threads = new ArrayList();
//用于唤醒辅助线程
private final Pipe wakeupPipe = Pipe.open();
//唤醒线程的文件描述符
private final int wakeupSourceFd;
private final int wakeupSinkFd;
private Object closeLock = new Object();
//保存文件描述符和SelectionKey的映射关系
private final WindowsSelectorImpl.FdMap fdMap = new WindowsSelectorImpl.FdMap();
//调用JNI的poll和处理就绪的SelectionKey
private final WindowsSelectorImpl.SubSelector subSelector = new WindowsSelectorImpl.SubSelector();
private long timeout;
private final Object interruptLock = new Object();
private volatile boolean interruptTriggered = false;
//新增的辅助线程使用该锁等待主线程的开始信号
private final WindowsSelectorImpl.StartLock startLock = new WindowsSelectorImpl.StartLock();
//主线程用该锁等待所有辅助线程执行完毕
private final WindowsSelectorImpl.FinishLock finishLock = new WindowsSelectorImpl.FinishLock();
private long updateCount = 0L;
SelectionKeyImpl封装了channel、selector和channel注册时的事件掩码interestOps等
final SelChImpl channel;
final SelectorImpl selector;
private int index;
private volatile int interestOps;
private int readyOps;
注册channel时,首先生成SelectionKeyImpl 对象
将SelectionKeyImpl 对象储存到channelArray数组中
将文件句柄和SelectionKeyImpl的对应关系加入到fdMap集合中
将selectionKey的文件描述符保存到pollWrapper对象中
protected void implRegister(SelectionKeyImpl var1) {
Object var2 = this.closeLock;
synchronized(this.closeLock) {
if (this.pollWrapper == null) {
throw new ClosedSelectorException();
} else {
//如果容量不够则扩容(后面会讲)
this.growIfNeeded();
//将key保存到channelArray数组中
this.channelArray[this.totalChannels] = var1;
//设置key在数组中的位置下标
var1.setIndex(this.totalChannels);
//在fdMap中保存句柄与key对象的映射
this.fdMap.put(var1);
//将key保存到keys集合中
this.keys.add(var1);
//将channel的文件描述符添加到pollWrapper对象中
this.pollWrapper.addEntry(this.totalChannels, var1);
++this.totalChannels;
}
}
}
class PollArrayWrapper {
//用户存储文件描述符数组的native空间
private AllocatedNativeObject pollArray;
//上述内存空间的地址
long pollArrayAddress;
//每条记录所占空间大小为8个字节
static short SIZE_POLLFD = 8;
//每条记录中文件描述符的相对起始偏移量
private static final short FD_OFFSET = 0;
//每条记录中事件掩码的相对起始偏移量
private static final short EVENT_OFFSET = 4;
//数组大小
private int size;
...
}
pollArrayWrapper对象使用使用native数组存放文件描述符和事件掩码,并将这块内存的地址保存到pollArrayAddress中,在调用poll时将该地址传递给JNI。
对象对外提供了有关FD、Event方法。
//添加文件描述符
void putDescriptor(int i, int fd) {
pollArray.putInt(SIZE_POLLFD * i + FD_OFFSET, fd);
}
//添加事件掩码
void putEventOps(int i, int event) {
pollArray.putShort(SIZE_POLLFD * i + EVENT_OFFSET, (short)event);
}
//获取事件掩码
int getEventOps(int i) {
return pollArray.getShort(SIZE_POLLFD * i + EVENT_OFFSET);
}
//获取文件描述符
int getDescriptor(int i) {
return pollArray.getInt(SIZE_POLLFD * i + FD_OFFSET);
}
在pollArray中,每条记录占8个字节,前四个字节存放文件描述符,接着使用两个字节存放等待事件的掩码,最后两个字节存放事件发生事件的掩码(目前未使用)
在注册通道前,首先判断是否需要扩容
每个线程最多只能管理1024个channel,每当channel超出1024则需要新增一个辅助线程,并将唤醒线程套接字文件描述符添加到辅助线程所管理的数组区域的第一个位置
private void growIfNeeded() {
if (this.channelArray.length == this.totalChannels) {
//如果channelArray数组容量不够则扩容两倍
int var1 = this.totalChannels * 2;
SelectionKeyImpl[] var2 = new SelectionKeyImpl[var1];
System.arraycopy(this.channelArray, 1, var2, 1, this.totalChannels - 1);
this.channelArray = var2;
//将pollWrapper对象的数组也扩容两倍
this.pollWrapper.grow(var1);
}
if (this.totalChannels % 1024 == 0) {
//如果数组中大小为每个线程能够处理的最大通道数量的整数倍,则将唤醒线程的文件描述符添加到totalChannels位置
this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, this.totalChannels);
++this.totalChannels;
//增加一个辅助线程
++this.threadsCount;
}
}
SubSelector对象封装了poll调用的逻辑和获取就绪selectionKey的方法。主线程和每个辅助线程都有自己的subSelector对象
readFds[0]存放该数组中元素个数
writeFds[0]存放该数组中元素个数
exceptFds[0]存放该数组中元素个数
private final class SubSelector {
//该线程管理的selectionKey数组区域的起始地址
private final int pollArrayIndex;
//可读文件描述符数组
private final int[] readFds;
//可写文件描述符数组
private final int[] writeFds;
//异常文件描述符数组
private final int[] exceptFds;
...
//主线poll调用所使用的方法
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);
}
//辅助线程poll调用所使用的方法
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);
}
private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);参数说明:
var1:数组起始偏移量
var3: 要处理的文件描述符数量
var4:readFds
var5:writeFds
var6:exceptFds
var7:处理超时时间
遍历数组,将就绪key添加到selectKeys集合中
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 {
//获取selector
WindowsSelectorImpl.MapEntry var9 = WindowsSelectorImpl.this.fdMap.get(var8);
if (var9 != null) {
//获取selectionKey
SelectionKeyImpl var10 = var9.ski;
if (!var5 || !(var10.channel() instanceof SocketChannelImpl) || !WindowsSelectorImpl.this.discardUrgentData(var8)) {
//如果key已经就绪则把事件累加到key
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 {
//如果key为就绪,则将key添加到selectKeys中
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;
}
首先忽略wakeupSourceFd,前面说了该文件描述符用于唤醒。
过滤fdMap不存在的文件描述符,这些文件描述符已经被取消了。
忽略OOB(紧急)数据,这些数据需要调用discardUrgentData读取并忽略。
根据key是否在SelectorKeys中决定是设置事件掩码还是更新事件掩码。
为了加快轮询速度,引入了多线程技术。每个线程负责最多管理1024个channel(实际上真正管理的只有1023个,其中有一个是用于唤醒的socket),前面讲过,在注册channel的时候讲过,每增加1023个channel的时候,创建一个辅助线程来处理这部分channel,每个辅助线程就是一个SelectThread对象
private final class SelectThread extends Thread {
//辅助线程序号
private final int index;
//每个线程都有一个SubSelector对象
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;
}
//1.等待主线程执行通知
//2.执行完后通知主线程执行完毕
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);
}
}
}
}
&emsp;等到主线程通知执行。每次线程执行完poll调用之后都需要等待主线程的下一次执行通知
private synchronized boolean waitForStart(SelectThread thread) {
while (true) {
//当runsCounter == thread.lastRun时等待主线程的执行通知
//新建SelectThread对象时 runsCounter = thread.lastRun
while (runsCounter == thread.lastRun) {
try {
startLock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
//如果isZombie()为真则线程退出runsCounter == thread.lastRun才成立
if (thread.isZombie()) { // redundant thread
return true; // will cause run() to exit.
} else {
//更新lastRun ,这样下一次执行时才满足runsCounter == thread.lastRun为真
thread.lastRun = runsCounter; // update lastRun
return false; // will cause run() to poll.
}
}
}
}
protected int doSelect(long var1) throws IOException {
if (this.channelArray == null) {
throw new ClosedSelectorException();
} else {
this.timeout = var1;
//移除已取消的key
this.processDeregisterQueue();
if (this.interruptTriggered) {
this.resetWakeupSocket();
return 0;
} else {
//调整辅助线程
this.adjustThreadsCount();
//重置finishLock
this.finishLock.reset();
//开始执行
this.startLock.startThreads();
try {
this.begin();
try {
//主线程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();
//删除已取消的key
this.processDeregisterQueue();
//更新selectKeys集合
int var3 = this.updateSelectedKeys();
//重置唤醒标志
this.resetWakeupSocket();
return var3;
}
}
}
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 var12) {
IOException var6 = new IOException("Error deregistering key");
var6.initCause(var12);
throw var6;
} finally {
var3.remove();
}
}
}
}
}
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);
}
//清空channelArray数组最后一项
this.channelArray[this.totalChannels - 1] = null;
--this.totalChannels;
if (this.totalChannels != 1 && this.totalChannels % 1024 == 1) {
//当最后一位为唤醒线程的文件描述符时,移除该记录,并减少一个辅助线程
--this.totalChannels;
--this.threadsCount;
}
//移除其他地方存储的key
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();
}
}
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()) {
//如果辅助线程多余则将多余的辅助线程的zombie标志置为true(相当于移除该辅助线程)
for(var1 = this.threads.size() - 1; var1 >= this.threadsCount; --var1) {
((WindowsSelectorImpl.SelectThread)this.threads.remove(var1)).makeZombie();
}
}
}
private synchronized void startThreads() {
++this.runsCounter;
//唤醒所有在waitForStart(this)方法中等到的辅助线程
this.notifyAll();
}
###等待所有辅助线程完成
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();
}
}
}
参照博文