【问题标题】:Interruptible network I/O in JavaJava中的可中断网络I/O
【发布时间】:2013-10-23 05:15:45
【问题描述】:

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

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

问题:

  1. 在使用旧式 I/O 时,是否从单独的线程 线程安全 关闭套接字?如果没有,有什么替代方案?
  2. 当使用 NIO 时,是否从单独的线程线程安全关闭套接字/通道?
  3. 在使用 NIO 和常规 IO 时,Socket.close() 的行为有什么不同吗?
  4. 使用 NIO 进行联网是否有任何好处除了可以通过简单地中断线程来终止阻塞的 I/O 操作(这样我不再需要保留对套接字的引用)?

【问题讨论】:

    标签: java multithreading io nio


    【解决方案1】:

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

    是的。

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

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

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

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

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

    除了可以通过简单地中断线程来终止阻塞的 I/O 操作(这样我不再需要保留对套接字的引用)之外,使用 NIO 进行网络还有什么好处?

    如何关闭连接是最不值得关注的。所以是的,有很多理由考虑 NIO 而不是普通 IO。

    蔚来的优点

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

    缺点

    • 普通 IO 更易于编码。
    • 出于这个原因,许多 API 仅支持普通 IO。

    【讨论】:

      【解决方案2】:

      晚会有点晚了,答案已经涵盖了主题,但我想我仍然可以添加一些有用的东西。

      我会尽量弄清楚 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。一方面,考虑到向后兼容性和接口编程,它应该是这样的。另一方面,实现方面的SocketAdaptor 在适应Socket 接口方面做得很差,至少在Oracle 的Java 8 中是这样。例如,SocketInputStream 并没有真正尝试将异常包装在它们的Socket 中相等的。这就是为什么当文档承诺抛出 SocketException 时您会看到 AsynchronousCloseException

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

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

        • 它是完全线程安全的。我已经提到了并行读取和写入(当您发送长数据流并且必须准备好在流程中接收来自另一端的消息时,这是异步协议的典型情况)。虽然它似乎与 IO 合作,但保证与 NIO 合作。

        • 您提到了通过中断线程来关闭套接字的能力,但它通常被低估了。有人可能认为Thread.interrupt()Socket.close() 之间没有太大区别,但实际上确实如此。如果您的线程不仅仅是 IO,您将不得不调用 both (顺序重要吗?不是很明显。)另一个考虑因素:如果您使用 Executors 进行线程处理(您应该),他们对你的套接字一无所知,但他们对中断线程一无所知。使用 NIO,您只需取消 Futureshutdown()Executor。使用 IO,您也必须以某种方式处理您的套接字。

        缺点:

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

        • NIO 违反 IO API 规范的另一种情况是 Socket.getRemoteSocketAddress() / SocketChannel.getRemoteAddress()。根据Socket docs,“如果套接字在关闭之前已连接,则此方法将在套接字关闭后继续返回连接的地址。”好吧,对于 NIO 来说,这不是真的。 SocketChannel.getRemoteAddress() 将抛出 ClosedChannelException,这是应该的,但 Socket.getRemoteSocketAddress() 将返回 null 而不是正确的地址。这似乎没什么大不了的,但它可能会在您最意想不到的地方导致 NPE。

      【讨论】:

        【解决方案3】:

        1) 由于引发异常的底层操作系统调用错误来自多线程感知的 TCP 堆栈,因此应该没有任何问题。

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

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

        4) 线程..或套接字。您必须保留对某些内容的引用。由于您正在关闭套接字,因此保留对它的引用似乎是合理的:)

        【讨论】:

          猜你喜欢
          • 2016-08-09
          • 1970-01-01
          • 1970-01-01
          • 2014-06-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-04-02
          • 2013-04-14
          相关资源
          最近更新 更多