Java NIO和多线程用于异步服务器

3

我一直在尝试将NIO与多线程读取处理配对,以使服务器具有可扩展性。由于存在一些低级客户端-服务器协议细节需要实现,因此我无法使用任何框架(例如Netty或MINA)。我刚刚检查了我的代码,并意识到这段代码存在潜在的竞态条件:

//executes in selector thread
public void runSelector() {
    //...
    Set<SelectionKey> keys = selector.selectedKeys();
    for (Iterator<SelectionKey> keyIter = keys.iterator(); keyIter.hasNext(); ) {
        final SelectionKey key = keyIter.next();
        keyIter.remove();
        if (key.isValid() && key.isReadable()) { //point A
            //maybe some other short calculations
            ((SocketChannel) key.channel()).read(buffer); //point B
            workerThreadPool.submit(new Runnable() { public void run() { processRead(key, buffer); } });
        }
    }
    //...
}

//executes in a worker thread
private void processRead(SelectionKey key, ByteBuffer buf) {
    //... somewhere
    key.cancel();
    //...
}

这是一个高度不可能的事件,但在工作线程中调用key.cancel()时,选择器线程正处于runSelector()方法中我注释的两个点之间是完全可能的。请记住,这可能部署在高并发机器上,并且运行选择器线程的CPU核心可能会被卡住。这是否是一个有效的问题,即在选择器线程中的key.isReadable()和channel.read()之间,工作线程可能会调用key.cancel()并导致CancelledKeyException?我应该有一些线程安全的集合来存储所有要取消的键,以便runSelector()可以在迭代结束时取消它们吗?像Netty或MINA这样的更专业的项目如何处理这种情况?
1个回答

2

锁定资源,

synchronized(key) {
    ((SocketChannel) key.channel()).read(buffer); //point B
}

如果这看起来有些奇怪,请查看Oracle关于并发的教程。请访问此链接

哇,我简直不敢相信我没有想到同步。我猜其中一个解决方案是用 synchronized 包装 key.cancel(),并且用 synchronized 包装 key.isValid() && key.isReadable() 块。非常感谢! - Kevin Jin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接