多线程技术Rox教程中的Java NIOServer真的安全吗?
{a1}有一个示例NIO服务器类。我不相信代码实际上是线程安全的。以下代码由某个用户类调用,以通过服务器发送数据:
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()给调用public send方法的人之前,发生了上下文切换,然后,可以一直保持写入状态,直到下一个数据被写入或准备好读取(选择器键不会更新为写入,选择器也不会接收到唤醒信号)。出于我的目的,这是不可接受的(如果是真的),但我不能仅仅为了适应这样一个边缘角落的情况而对功能进行太多的更改(比如通过向select添加超时)。我试图想出一种方法来更好地同步这些部分,或者确保在上下文切换发生之前调用select,但我被卡住了
编辑:由于对并发问题的一些困惑,我将在这里更清楚地说明它
运行线程:进入同步块,没有挂起的更改
运行线程:退出同步块
调度程序:上下文切换
其他线程:调用send方法
其他线程:将挂起的更改和挂起的数据排入队列
其他线程:调用选择器。醒来
调度程序:上下文切换
运行线程:选择器上的块。选择(),忽略挂起的数据
Run thread:如果没有人再次尝试使用socket,它将继续永久阻止
我认为这涉及NIO和多线程的原因是,我正在寻找一种正确同步选择器的方法。select()方法
# 1 楼答案
及
不真实。见Javadoc:
# 2 楼答案
关闭CPU并不意味着锁突然丢失,而是在重新打开CPU时“重新获得”
假设您被调出CPU,另一个打算更改“pendingChanges”的线程在退出关键块之前以某种方式被给定了周期,与锁相关的ram必须由前进的线程访问,然后它将看到锁和块
最终,CPU的调度程序会注意到线程进入阻塞(等待)状态,并将其解除优先级。持有锁的线程迟早会获得足够的优先级,以便在CPU上重新调度并释放锁
在任何情况下,只有一个线程将访问
this.pendingChanges
,而该线程将是唯一能够对其进行变异的线程