Java NIO 遍历时为何要remove()?
2019-03-28 本文已影响0人
我不吃甜食
为何要remove()
NIO模型如下
while(selector.select()>0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove(); //为何要删除?
if(selectionKey.isAcceptable()){
....
}else if(...){
....
}
}
}
测试结果:一个客户端进行测试
如果不删除,则下次select()就返回0,跳出循环 ;
跟一下select()的源码,发现更新selectedKeys的操作在KQueueSelectorImpl.java的updateSelectedKeys()里 (mac下是KQueueSelectorImpl.java,linux的类不同,但逻辑基本相同)
private int updateSelectedKeys(int entries)
throws IOException
{
int numKeysUpdated = 0;
boolean interrupted = false;
// A file descriptor may be registered with kqueue with more than one
// filter and so there may be more than one event for a fd. The update
// count in the MapEntry tracks when the fd was last updated and this
// ensures that the ready ops are updated rather than replaced by a
// second or subsequent event.
updateCount++;
for (int i = 0; i < entries; i++) {
int nextFD = kqueueWrapper.getDescriptor(i);
if (nextFD == fd0) {
interrupted = true;
} else {
MapEntry me = fdMap.get(Integer.valueOf(nextFD));
// entry is null in the case of an interrupt
if (me != null) {
int rOps = kqueueWrapper.getReventOps(i);
SelectionKeyImpl ski = me.ski;
if (selectedKeys.contains(ski)) {
// 不删除会走这里
if (me.updateCount != updateCount) {
if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
numKeysUpdated++;
me.updateCount = updateCount;
}
} else {
// ready ops have already been set on this update
ski.channel.translateAndUpdateReadyOps(rOps, ski);
}
} else {
//删除会走到这里
ski.channel.translateAndSetReadyOps(rOps, ski);
if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
selectedKeys.add(ski);
numKeysUpdated++;
me.updateCount = updateCount;
}
}
}
}
}
if (interrupted) {
// Clear the wakeup pipe
synchronized (interruptLock) {
IOUtil.drain(fd0);
interruptTriggered = false;
}
}
return numKeysUpdated; //select()的返回值
}
从上面的代码可看出,最后的返回值是不是为0,与translateAndSetReadyOps()方法有关,这个方法又调用了translateReadyOps()
public boolean translateReadyOps(int ops, int initialOps,
SelectionKeyImpl sk) {
int intOps = sk.nioInterestOps(); // Do this just once, it synchronizes
int oldOps = sk.nioReadyOps();
int newOps = initialOps;
// ops为新到来的事件,可能包含多种事件
if ((ops & PollArrayWrapper.POLLNVAL) != 0) {
return false;
}
if ((ops & (PollArrayWrapper.POLLERR
| PollArrayWrapper.POLLHUP)) != 0) {
newOps = intOps;
sk.nioReadyOps(newOps);
// No need to poll again in checkConnect,
// the error will be detected there
readyToConnect = true;
return (newOps & ~oldOps) != 0;
}
// 可读事件
if (((ops & PollArrayWrapper.POLLIN) != 0) &&
((intOps & SelectionKey.OP_READ) != 0) &&
(state == ST_CONNECTED))
newOps |= SelectionKey.OP_READ;
//连接事件
if (((ops & PollArrayWrapper.POLLCONN) != 0) &&
((intOps & SelectionKey.OP_CONNECT) != 0) &&
((state == ST_UNCONNECTED) || (state == ST_PENDING))) {
newOps |= SelectionKey.OP_CONNECT;
readyToConnect = true;
}
//可写事件
if (((ops & PollArrayWrapper.POLLOUT) != 0) &&
((intOps & SelectionKey.OP_WRITE) != 0) &&
(state == ST_CONNECTED))
newOps |= SelectionKey.OP_WRITE;
// 设置准备好的事件
sk.nioReadyOps(newOps);
//这一步很关键
return (newOps & ~oldOps) != 0;
}
最后一句是关键: 比如 newOps 设为了op_read, 而oldOps也是op_read,则会返回false; 最后一句的逻辑可用下面的表格描述
老readyOps | 新readyOps | select() 返回值 | 描述 |
---|---|---|---|
write | read | > 0 | 事件完全改变 |
read | write and read | > 0 | 事件增加 |
write and read | connect | > 0 | 事件完全改变 |
write and read | read | = 0 | 事件减少 |
read | read | = 0 | 事件不变 |
如果remove了,则可以认为老readyOps为0,此时只要有事件,最后一句就返回true,所以remove()不会丢失事件。
结论
不remove(),不一定有问题。但是remove()了,肯定不会出现问题。