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()方法的方法。
答案 0 :(得分:3)
选择器不会接收唤醒信号
和
然后尝试唤醒选择器(因为选择器未被选中而无效)
不真实的。请参阅Javadoc:
如果当前没有选择操作正在进行,那么除非在此期间调用
selectNow()
方法,否则将立即返回其中一个方法的下一次调用。
答案 1 :(得分:0)
关闭CPU并不意味着锁定会突然丢失,而是“重新获得”"当你重新打开CPU时。
假设你被交换掉CPU而另一个线程意味着改变" pendingChanges"不知何故,在退出临界块之前给出了循环,与锁相关的ram必须由前进的线程访问,然后它将看到锁定和阻塞。
最终CPU的调度程序会注意到线程进入阻塞(等待)状态,并对其进行优先级排序。持有锁的线程迟早应获得足够的优先权,以便重新安排到CPU上并释放锁定。
在任何情况下,只有一个线程将访问epub_content
,并且该线程将是唯一能够改变它的线程。