根据 Javadoc,
It returns only after at least one channel is selected, this selector's wakeup method is invoked, the current thread is interrupted, or the given timeout period expires, whichever comes first.
但偶尔它会在没有这 4 种情况的情况下返回:
- 至少选择了一个 channel :返回0
- 唤醒方法被调用:
wakeup
没有被调用 - 当前线程被中断:Thread.interrupted() 返回 false
- given timeout period expires:根据日志未过期
2016 年 3 月 15 日更新
在第 392 行和第 402 行的源代码中,我添加了一些日志: https://github.com/xqbase/tuna/blob/debug/core/src/main/java/com/xqbase/tuna/ConnectorImpl.java
public boolean doEvents(long timeout) {
Log.v("Before Select: " + timeout);
int keySize;
try {
keySize = timeout == 0 ? selector.selectNow() :
timeout < 0 ? selector.select() : selector.select(timeout);
} catch (IOException e) {
throw new RuntimeException(e);
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
if (keySize == 0) {
Log.v("After Select(0): selectedKeys=" + selectedKeys.size() + ", " +
"interrupt=" + Thread.interrupted());
invokeQueue();
return false;
}
for (SelectionKey key : selectedKeys) {
...
这是日志:
...
2016-03-15 23:07:49.695 com.xqbase.tuna.ConnectorImpl doEvents
FINE: Before Select: 8120
2016-03-15 23:07:49.696 com.xqbase.tuna.ConnectorImpl doEvents
FINE: After Select(0): selectedKeys=0, interrupt=false
2016-03-15 23:07:49.696 com.xqbase.tuna.ConnectorImpl doEvents
FINE: Before Select: 8119
2016-03-15 23:07:49.696 com.xqbase.tuna.ConnectorImpl doEvents
FINE: After Select(0): selectedKeys=0, interrupt=false
2016-03-15 23:07:49.700 com.xqbase.tuna.ConnectorImpl doEvents
FINE: Before Select: 8115
2016-03-15 23:07:49.701 com.xqbase.tuna.ConnectorImpl doEvents
FINE: After Select(0): selectedKeys=0, interrupt=false
2016-03-15 23:07:49.701 com.xqbase.tuna.ConnectorImpl doEvents
FINE: Before Select: 8114
2016-03-15 23:07:49.702 com.xqbase.tuna.ConnectorImpl doEvents
FINE: After Select(0): selectedKeys=0, interrupt=false
...
很奇怪:没有选中键,没有中断,没有超时,也没有唤醒,但是它返回了。
Java 中有错误吗?我的 Java 版本是 1.8.0_51-b16(64 位服务器虚拟机),在 CentOS 6.5 x64 linode 上运行。
最佳答案
这可能真的是 JDK 中的一个错误。 Netty 和 Mina 好像也遇到了这样的问题,他们重建选择器作为解决方法。
查看最新的 Netty 代码 https://github.com/netty/netty/blob/4.1/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java L641-681:
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
// - Selected something,
// - waken up by user, or
// - the task queue has a pending task.
// - a scheduled task is ready for processing
break;
}
...
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
// The selector returned prematurely many times in a row.
// Rebuild the selector to work around the problem.
logger.warn(
"Selector.select() returned prematurely {} times in a row; rebuilding selector.",
selectCnt);
rebuildSelector();
selector = this.selector;
// Select again to populate selectedKeys.
selector.selectNow();
selectCnt = 1;
break;
}
参见 Mina 2.0 代码 https://github.com/apache/mina/blob/2.0/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java L1070-1092:
if (!wakeupCalled.getAndSet(false) && (selected == 0) && (delta < 100)) {
// Last chance : the select() may have been
// interrupted because we have had an closed channel.
if (isBrokenConnection()) {
LOG.warn("Broken connection");
} else {
LOG.warn("Create a new selector. Selected is 0, delta = " + (t1 - t0));
// Ok, we are hit by the nasty epoll
// spinning.
// Basically, there is a race condition
// which causes a closing file descriptor not to be
// considered as available as a selected channel,
// but
// it stopped the select. The next time we will
// call select(), it will exit immediately for the
// same
// reason, and do so forever, consuming 100%
// CPU.
// We have to destroy the selector, and
// register all the socket on a new one.
registerNewSelector();
}
}
因此,如果 select() 返回意外的零,注册一个新的选择器可能是最佳实践。
关于java - Selector.select(timeout) 在超时前返回 0,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35858537/