题
如果我写:
int selectedChannels = selector.select(); Set selectedKeys = selector.selectedKeys(); if ( selectedChannels != selectedKeys.size() ) { // Selector.select() returned because of a call to Selector.wakeup() // so do synchronization. } // Continue with handling selected channels.
它能正确检测到唤醒呼叫吗?
背景资料:
我正在编写一个服务器,大多数时候它只是接收数据包并将它们存储在文件中。应用程序很少需要向自己发送特殊数据包。为此,它启动到服务器套接字的连接(从不同的线程):
SocketChannel channel = SocketChannel.open(); channel.configureBlocking( false ); channel.connect( new InetSocketAddress( InetAddress.getLocalHost(), PORT )); selector.wakeup(); SelectionKey key = channel.register( selector, SelectionKey.OP_CONNECT );
问题是如果主线程已经在 Selector.select() 中,SelectableChannel.register() 可能会阻塞。为了防止这种情况发生,我调用 Selector.wakeup(),它让主线程从 select() 提前返回。为了确保其他线程有机会完成寄存器调用,我必须同步主线程,但我必须在 每一个 从 select() 返回。如果我可以检测它是否由于wakeup()调用而从select()返回,那么我可以针对这种情况对其进行优化。
因此,理论上顶部代码片段应该可以工作,但我想知道它是否只能这样做,因为它依赖于一些未指定的行为?
感谢您的任何提示。
解决方案
我猜想,根据合同,所提议的片段原则上根本行不通 Selector#select()
和 Selector#selectedKeys()
. 。从 选择器:
- 所选键集是这样的键集,使得每个键的通道被检测为准备好用于在之前的选择操作期间在该键的兴趣集中标识的操作中的至少一个。该集合由 selectedKeys 方法返回。
public abstract int select(long timeout) throws IOException Returns: The number of keys, possibly zero, whose ready-operation sets were updated
当我读到这篇文章时, selectedKeys
set 应始终等于返回的数字 select
根据定义。我注意到 - 正如您可能也注意到的那样 - 某些实现并不完全遵循文档,事实上 selectedKeys
返回具有更新的就绪操作集的所有键,即使它们在调用期间没有更新 select
. 。select 由于调用而唤醒的唯一其他指标 wakeup
可能是键的数量为零;然而,这两种方法充其量都是不可靠的。
正如所暗示的,处理这个问题的通常方法是通过并发控制。我不会担心这里的执行时间;这是一个经典的例子 过早优化.
除非您真的担心个位数微秒的容差,否则您不会注意到任何减速 - 如果您担心这种容差水平, Selector
无论如何对你来说都不够可靠。
这是常用机制的示例,使用 ReentrantLock
实现适当的并发性:
ReentrantLock selectorGuard;
Selector selector;
private void doSelect() {
// Don't enter a select if another thread is in a critical block
selectorGuard.lock();
selectorGuard.unlock();
selector.select();
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
while(keyIter.hasNext()) {
SelectionKey key = keyIter.next();
keyIter.remove();
// Process key
}
}
private void addToSelector() {
// Lock the selector guard to prevent another select until complete
selectorGuard.lock();
try {
selector.wakeup();
// Do logic that registers channel with selector appropriately
} finally {
selectorGuard.unlock();
}
}
其他提示
我不明白为什么你的代码通常可以工作。
为什么不直接检查一个 volatile
后 select
?
如果select()
返回零,要么超时或它被唤醒。
您不能真正确保所选择醒来的唯一原因是由于警醒。您还可能有套接字活动。
所以,你需要唤醒的调用者也做类似设置挥发性布尔值,表明其对关注的渴望。所述选择器环可以检查此布尔每次的值它唤醒。