Java中的可中断网络I / O.

时间:2013-10-15 11:54:51

标签: java multithreading io nio

在Java 1.4+中,有三种方法可以中断在套接字I / O上阻塞的流:

  1. 如果套接字是使用常规java.net.Socket(InetAddress, int)构造函数创建的,我可以从单独的线程中close it。结果,在被阻止的线程中抛出SocketException
  2. 如果套接字是使用SocketChannel.open(...)创建的。socket()(非阻塞I / O) - 再次,可以从单独的线程关闭它,但现在是一个不同的异常({ {3}})被抛出阻塞的线程。
  3. 此外,如果使用非阻塞I / O,则可以在抛出AsynchronousCloseException的情况下中断被阻塞的线程。使用旧式Java I / O时中断被阻塞的线程对该线程没有影响。
  4. 问题:

    1. 使用旧式I / O时,是否从单独的线程线程安全关闭套接字?如果没有,有哪些替代方案?
    2. 使用NIO时,是从单独的线程线程安全关闭套接字/通道吗?
    3. 使用NIO而不是常规IO时,ClosedByInterruptException行为有什么不同吗?
    4. 使用NIO进行网络之外还有什么好处可以通过简单地中断线程来终止阻塞的I / O操作(这样我就不再需要保持对套接字的引用)?

3 个答案:

答案 0 :(得分:3)

  

使用旧式I / O时,是否从单独的线程线程安全关闭套接字?如果没有,有哪些替代方案?

另一种方法是使用阻塞NIO(这是SocketChannel BTW的默认行为)我更喜欢这个用于少量连接,因为它具有NIO的效率,但是普通IO的一些简单性。

  

在使用NIO时,是否从单独的线程线程安全关闭套接字/通道?

对于阻塞NIO和无阻塞NIO,它们都是线程安全的。

  

使用NIO而不是常规IO时,Socket.close()行为有什么不同吗?

如果您需要了解详细信息,我建议您阅读代码,但基本上它们是相同的。

  

使用NIO进行联网是否有任何好处,除了通过简单地中断线程来终止阻塞的I / O操作(以便我不再需要保留对套接字的引用)?

如何关闭连接是最不重要的问题。所以,是的,有很多理由认为NIO优于普通的IO。

NIO的优点

  • 使用直接内存时速度更快,重量更轻
  • 可扩展到数万用户。
  • 支持高效的忙碌等待。

缺点

  • 普通IO更易于编码。
  • 因此,许多API仅支持普通IO。

答案 1 :(得分:1)

聚会晚些,答案已经涵盖了该主题,但是我认为我仍然可以添加一些有用的内容。

我将尝试弄清楚API规范做出了哪些保证,以及具体实现是什么(有关详细信息,请使用Oracle的Java 8源)。例如,如果Oracle Java 8中的某些内容是安全的,但API不能保证这一点,则它可能会突然中断其他供应商的实现或Java的其他版本。

TL; DR:即使对于阻止基于流的IO,NIO也要好得多。只要确保使用Socket.getInputStream()Socket.getOutputStream()而不是Channels.newInputStream()Channels.newOutputStream()。并针对可能的API规范违规(例如抛出错误的异常或意外返回null)进行防御性代码。

  1. 是的,老式的Socket.close()是线程安全的。

    明智的使用API​​:引用文档,“此套接字上当前在I / O操作中阻塞的任何线程都将抛出SocketException。”它没有在任何地方说“安全”,但是没有线程安全性,这样的保证是不可能的。

    实施方面:关于线程安全性,Oracle的Java 8 Socket实现非常糟糕。在随机位置使用或未使用很多不同的锁。一个主要的问题是是否可以在两个线程之间的同一套接字上拆分读写。 似乎可以正常工作,但API规范无法保证,并且代码看起来随时可能会随机中断。但是,就close()而言,它是通过锁定Socket本身和一些内部锁定来保护的,从而使其真正安全。

    有一个(明显的)警告,尽管:close()本身是线程安全的,但是为了使其按预期工作,访问套接字实例时必须遵循常规的线程安全规则,即,应该将其正确发布到执行close()的线程中。

  2. NIO在规范和实际实现中通常都是非常线程安全的。 close()没什么不同。在实现方面,SocketChannel.socket()返回SocketAdaptor的实例(这是适配器模式的一种情况),使SocketChannel适应Socket API。 NIO完全不使用旧的Socket实现,所有Socket操作都委托给基础的SocketChannel

  3. API在这里几乎没有帮助,因为SocketChannel.socket()“正式”返回了对Socket的引用,该文档的文档完全没有提到NIO。一方面,它是应有的,考虑了向后兼容性和接口编程。另一方面,虽然至少在Oracle Java 8中,实现方面的SocketAdaptor在适应Socket接口方面做得很差。例如,SocketInputStream并没有真正尝试包装Socket等价的例外。这就是为什么当文档承诺会抛出AsynchronousCloseException时看到SocketException的原因。

    好消息是,NIO实现通常更好。因此,只要您不介意抛出错误的异常(以及为什么有人会关心它是哪种IOException?),NIO Socket就会发挥作用。就close()而言,它只是关闭了关联的频道,因此,无论您呼叫Socket.close()还是SocketChannel.close()都没有关系。

  4. @Peter Lawrey很好地介绍了NIO-vs-IO的优缺点。因此,我假设不是在 general 中谈论NIO-vs-IO,而是假设我们在 only 中使用基于流的IO进行阻塞。在这种情况下,NIO具有以下优点:

    • 这是完全线程安全的。我已经提到过并行读写(异步协议的一种典型情况,当您发送长数据流并且必须准备好从过程的另一端接收消息时)。虽然可以让IO使用 ,但可以保证与NIO可以使用

    • 您提到了通过中断线程来关闭套接字的功能,但是它经常被低估。有人可能认为Thread.interrupt()Socket.close()之间的区别不大,但实际上是。如果您的线程不仅要执行IO操作,还必须调用两者(顺序很重要吗?不是很明显。)另一个注意事项:如果您使用Executors进行线程处理(您应该),他们对您的套接字一无所知,但他们对中断线程一无所知。使用NIO,您只需取消Futureshutdown()Executor。使用IO,您还必须以某种方式处理套接字。

    缺点:

    • 将NIO与IO混合可能很棘手。您可能会认为SocketChannel.socket().getInputStream()等同于Channels.newInputStream(SocketChannel)。好吧,不是。前者支持SocketTimeoutException,后者不支持(它将永远阻止)。当您应该接收心跳消息并停止连接时关闭协议时,这对于协议非常重要。

    • NIO违反IO API规范的另一种情况是Socket.getRemoteSocketAddress() / SocketChannel.getRemoteAddress()。根据{{​​1}}文档,“如果套接字在关闭之前已连接,则此方法将在套接字关闭后继续返回连接的地址。”好吧,对于NIO而言,这是不正确的。 Socket将按应有的方式抛出SocketChannel.getRemoteAddress(),但是ClosedChannelException将返回Socket.getRemoteSocketAddress()而不是正确的地址。这似乎没什么大不了,但可能会在您最不希望的地方导致NPE。

答案 2 :(得分:0)

1)由于异常的OS调用错误来自异常识别的TCP堆栈,因此应该没有任何问题。

2)不确定 - 没有尝试过,但如果有任何问题会很惊讶。

3)可能存在一些性能差异。操作系统close()调用需要与TCP对等方进行4次握手 - 不确定哪种操作系统以非阻塞方式支持(与连接相同)。

4)Thread..or socket。你必须保持对某事的参考。由于您正在关闭套接字,因此保持对它的引用似乎是合理的:)