异步字节通道的线程问题

3
AsynchronousByteChannel.read() 的Javadoc表示该操作是异步执行的,但当到达流的末尾时会发生什么呢?实现是否允许在调用read()的同一线程中触发完成处理程序?从实现的角度来看,没有理由异步地执行此操作,因为我们已经知道了结果。同样,如果用户尝试读取remaining()返回0的ByteBuffer,我们知道读取操作必须返回0。
我之所以提出这个问题,是因为我在自己的AsynchronousByteChannel实现中遇到了竞争条件。当操作完成时,我会调用一个完成处理程序,该处理程序会在自身上调用notify()。然后我调用以下用户代码:
CompletionHandler<?, ?> handler = ...;
synchronized (handler)
{
  asyncByteChannel.read(handler);
  handler.wait();
}

注意,用户假设处理程序将在操作完成时得到通知,但由于read()实际上会同步调用完成处理程序,因此它会在wait()之前得到通知,后者将永远阻塞。
规范是否要求我在单独的线程中更新CompletionHandler,还是用户应该意识到调用read()的线程可能会同步执行一些操作?

1
它的线程模型很混乱。关于异步组的Javadoc无法解析。线程应该交给开发者处理 - 那是容易的部分。不幸的是,他们决定帮助我们处理线程。结果变得非常复杂、难以理解,并且缺乏文档(因为他们无法解释清楚混乱之处)。一位不经意的评论者可能认为API还好 - 的确,为什么会有困难呢?但当有人认真地在其之上构建应用时,这些复杂性将使他抓狂。 - irreputable
2个回答

1
即使处理程序在另一个线程上被调用,也不能保证它会在read方法返回后或者在你的wait()开始后被调用。(好吧,同步锁似乎可以保证这一点。)
你应该使用同步和布尔变量来进行等待和锁定:
CompletionHandler<?, ?> handler = ...;
synchronized (handler)
{
   asyncByteChannel.read(handler);
   while(!handler.finished) {
     handler.wait();
   }
}

...然后您的处理程序将把finished变量设置为true。


如果我被允许在主线程上处理CompletionHandler,那么你提出的解决方案非常好。但目前还不清楚是否可以这样做 :) - Gili
不,解决方案总是可用的。(如果不允许的话并非必要,但您仍然应该等待循环,因为可能会出现虚假唤醒。) - Paŭlo Ebermann
我同意,但这不是这个问题的重点。它关于规范是否允许在同一线程上通知。 - Gili

1

查看 http://www.docjar.com/html/api/sun/nio/ch/AsynchronousSocketChannelImpl.java.html 发现他们总是在单独的线程中更新 CompletionHandler,但 Future 在相同的线程中更新。搜索变量 hasSpaceToRead 以找到相关方法。

我猜他们的思路大致如下:

  1. 我们创建返回给用户的 Future,因此在返回对象之前没有办法与其交互(同步等)。
  2. 用户创建 CompletionHandler,因此我们无法控制实现执行的操作(我们可能会触发死锁!)。不要冒险,使用单独的线程触发完成处理程序。

更新:我被更正了。根据 Invoker.invoke() "如果当前线程在通道组的线程池中,则直接调用处理程序,否则间接调用处理程序。"

已解决:根据http://download.oracle.com/javase/7/docs/api/java/nio/channels/AsynchronousChannelGroup.html,“当I/O操作立即完成,并且启动线程是组中的池化线程之一时,则完成处理程序可以直接由启动线程调用。”


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