【发布时间】:2012-03-30 09:05:44
【问题描述】:
我的 Java NIO 选择器是使用 select() 实现的,因此它会一直阻塞,直到出现以下任何一种情况:
- 已注册的频道已准备就绪
- 是wakeup()'ed
- 线程被中断
由此,我对select()返回0的情况做了一些假设:
- 一定是原因 2. 或 3.
-
selectedKeys() 应该返回一个空的
ResultSet - 我不需要调用
selectedKeys(),可以继续进行下一次循环迭代,此时select()将再次被调用
然而,我遇到了select()返回0的情况,尽管有一个准备好的频道。 selectedKeys() 按预期返回一个 Set 和 1 SelectionKey。
即使对select() 的多次调用也总是返回0,直到通道被处理并且SelectionKey 被删除。这种情况基本上以无限循环结束,因为select() 不会阻塞,而是总是立即返回 0。
简化代码:
Selector selector = Selector.open();
SocketChannel channel;
for (...) { // for each node
// Create and connect channels...
...
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ, someRelatedObject);
}
int ready;
Set<SelectionKey> readyKeys;
while (true) {
ready = selector.select();
readyKeys = selector.selectedKeys();
System.out.println("Ready channels: " + ready);
System.out.println("Selected channels: " + readyKeys.size());
if (ready == 0) {
continue;
}
for (SelectionKey key : readyKeys) {
if (key.isValid() && key.isReadable()) {
// Take action...
}
readyKeys.remove(key);
}
}
为什么select()虽然有一个准备好的频道却返回0?
建议的处理方法是什么?
编辑:
改变这个:
for (SelectionKey key : readyKeys) {
if (key.isValid() && key.isReadable()) {
// Take action...
}
readyKeys.remove(key);
}
到这里
for (SelectionKey key : readyKeys) {
readyKeys.remove(key);
if (key.isValid() && key.isReadable()) {
// Take action...
}
}
解决了这个问题。在某些情况下,代码会在 remove() 密钥之前 continue for 循环。
编辑 2:
我最近才知道我的 foreach 循环在选定的键集上很糟糕。 foreach 使用集合的迭代器。在迭代集合时直接修改集合(而不是通过迭代器的方法)可能会导致“任意的、不确定的”行为。
选定的键集可以提供 fail-fast 迭代器。 Fail-fast 迭代器检测到此类修改并在下一次迭代时抛出ConcurrentModificationException。因此,修改 foreach 中的集合可能会导致行为不确定或导致异常 - 取决于迭代器的实现。
解决方案:不要使用 foreach。使用迭代器并通过iterator.remove() 删除密钥。
Iterator<SelectionKey> iterator;
SelectionKey key;
while (true) {
// ...
iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
key = iterator.next();
iterator.remove();
// ...
}
}
【问题讨论】: