Rox Tutorial中的Java NIOServer实际上是否安全?

时间:2016-08-22 19:08:48

标签: java multithreading nio

Rox Java NIO Tutorial有一个示例NIO Server类。我不相信代码实际上是线程安全的。以下代码由某个用户类调用,以通过服务器发送数据:

public void send(SocketChannel socket, byte[] data) {
    synchronized (this.pendingChanges) {
        this.pendingChanges.add(new 
            ChangeRequest(socket, ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE));

        synchronized (this.pendingData) {
            List queue = (List) this.pendingData.get(socket);
            if (queue == null) {
                queue = new ArrayList();
                this.pendingData.put(socket, queue);
            }
            queue.add(ByteBuffer.wrap(data));
        }
    }
    this.selector.wakeup();
}

这是他们用于run()方法的相关代码:

while (true) {
    try {
        synchronized (this.pendingChanges) {
            Iterator changes = this.pendingChanges.iterator();
            while (changes.hasNext()) {
                ChangeRequest change = (ChangeRequest) changes.next();
                switch (change.type) {
                    case ChangeRequest.CHANGEOPS:
                    SelectionKey key = change.socket.keyFor(this.selector);
                    key.interestOps(change.ops);
                }
            }
            this.pendingChanges.clear();
        }

        this.selector.select();
        ...
    }
}

我的问题是,如果在主循环期间发生了上下文切换,在同步块之后但在调用select()之前发生了调用公共发送方法的人,那么它可能只是坚持到写入直到下一个数据被写入或准备好被读取(选择器键不会被更新以写入,并且选择器不会接收唤醒信号)。对于我的目的而言,这是不可接受的(如果是真的),但我不能仅仅为了适应这样的边缘角落情况而改变功能(例如通过向选择添加超时)。我试图想出一种方法来更好地同步这些部分,或者确保在上下文切换发生之前调用select,但是我被卡住了。

编辑:由于对并发问题的一些困惑,我将在这里更清楚地说明。

运行线程:进入同步块,没有挂起的更改。

运行线程:退出同步块。

调度程序:上下文切换。

其他线程:调用send方法。

其他主题:将挂起的更改和待处理数据排入队列

其他线程:调用selector.wakeup()

调度程序:上下文切换。

运行线程:在selector.select()上阻塞,无视待处理数据。

运行线程:如果没有人再次尝试使用套接字,则继续阻塞。

我认为这涉及NIO和多线程的原因是因为我正在寻找一种正确同步selector.select()方法的方法。

2 个答案:

答案 0 :(得分:3)

  

选择器不会接收唤醒信号

  

然后尝试唤醒选择器(因为选择器未被选中而无效)

不真实的。请参阅Javadoc

  

如果当前没有选择操作正在进行,那么除非在此期间调用selectNow()方法,否则将立即返回其中一个方法的下一次调用。

答案 1 :(得分:0)

关闭CPU并不意味着锁定会突然丢失,而是“重新获得”"当你重新打开CPU时。

假设你被交换掉CPU而另一个线程意味着改变" pendingChanges"不知何故,在退出临界块之前给出了循环,与锁相关的ram必须由前进的线程访问,然后它将看到锁定和阻塞。

最终CPU的调度程序会注意到线程进入阻塞(等待)状态,并对其进行优先级排序。持有锁的线程迟早应获得足够的优先权,以便重新安排到CPU上并释放锁定。

在任何情况下,只有一个线程将访问epub_content,并且该线程将是唯一能够改变它的线程。