2011年的Java:线程套接字VS NIO:在64位操作系统和最新Java版本上选择什么?

2011年的Java:线程套接字VS NIO:在64位操作系统和最新Java版本上选择什么?,java,networking,io,nio,Java,Networking,Io,Nio,我在StackOverflow和一些博客上读过几篇关于java.net和java.nio的文章。但我仍然不知道什么时候应该选择NIO而不是线程套接字。你能检查一下我下面的结论并告诉我哪些是错误的,哪些是遗漏的吗 由于在线程模型中,您需要为每个活动连接指定一个线程,并且每个线程的堆栈占用大约250KB的内存,因此在每个套接字线程模型中,在大量并发连接上,您将很快耗尽内存。不像NIO 在现代操作系统和处理器中,大量活动线程和上下文切换时间对性能几乎没有影响 NIO throughtput可能更低,

我在StackOverflow和一些博客上读过几篇关于java.net和java.nio的文章。但我仍然不知道什么时候应该选择NIO而不是线程套接字。你能检查一下我下面的结论并告诉我哪些是错误的,哪些是遗漏的吗

  • 由于在线程模型中,您需要为每个活动连接指定一个线程,并且每个线程的堆栈占用大约250KB的内存,因此在每个套接字线程模型中,在大量并发连接上,您将很快耗尽内存。不像NIO

  • 在现代操作系统和处理器中,大量活动线程和上下文切换时间对性能几乎没有影响

  • NIO throughtput可能更低,因为异步NIO库在高负载环境中使用的select()和poll()比唤醒和休眠线程更昂贵

  • NIO总是比较慢,但它允许您处理更多并发连接。这本质上是一种时间/空间权衡:传统IO速度更快,但内存占用更大;NIO速度较慢,但使用的资源更少

  • Java对每个并发线程的硬限制为15000/30000,具体取决于JVM,这将把每个连接模型的线程数限制为最大并发连接数,但JVM7没有这样的限制(无法确认此数据)

因此,作为结论,您可以得出以下结论:

  • 如果您有成千上万个并发连接,那么NIO是一个更好的选择,除非请求处理速度是您的关键因素
  • 如果您的线程数少于此数,则每个连接的线程数是一个更好的选择(假设您能够提供最大数量的RAM来容纳所有并发线程的堆栈)
  • 对于Java7,您可能希望在任何一种情况下都使用NIO2.0

我说的对吗?

这对我来说似乎是对的,除了关于Java限制线程数量的部分——这通常受到它所运行的操作系统的限制(请参阅和)


要达到这么多线程,您可能需要调整JVM的堆栈大小。

我仍然认为传统IO中线程的上下文切换开销非常大。在较高的级别上,如果多个线程不会争夺相同的资源,或者它们花费的时间远远高于资源上的上下文切换开销,那么您只能使用多个线程来获得性能。 之所以提出这个问题,是因为有了新的存储技术,如SSD,您的线程可以更快地在CPU上进行竞争

构建NIO服务器没有一种“最佳”方法,但这一特殊问题的优势表明人们认为有!您的问题总结了适合这两个选项的用例,足以帮助您做出适合您的决策


此外,混合解决方案也是可能的!当线程要做一些值得花费的事情时,您可以将通道交给线程,当更好时,您可以坚持使用NIO。

我会说,从每个连接的线程开始,如果遇到问题,可以从那里进行调整

如果您确实需要处理一百万个连接,则应该考虑编写(或查找)C中的简单请求代理(或任何),这将比每个java实现都使用更少的连接。代理可以异步接收请求,并将它们排队给用您选择的语言编写的后端工作程序

因此,每个活动请求的后端只需要一个线程,并且您可以只拥有固定数量的线程,以便在某种程度上预先确定内存和数据库的使用。当大量请求并行运行时,请求会等待更长的时间

因此,我认为您永远不应该在64位系统上求助于NIO选择通道或异步I/O(NIO 2)。每个连接的线程模型工作得很好,您可以使用一些更合适的低级技术将连接扩展到“几十个或几十万个”


避免过早优化(即在大量连接进入之前编写NIO代码)总是很有帮助的,如果可能的话,不要重新设计轮子(Jetty、nginx等)。

最常被忽视的是NIO允许零拷贝处理。例如,如果您在一台服务器上使用老式套接字从多个进程中侦听相同的多播流量,则任何多播数据包都会从网络/内核缓冲区复制到每个侦听应用程序。因此,如果你构建一个由20个进程组成的网格,你就会遇到内存带宽问题。使用nio,您可以检查传入的缓冲区,而无需将其复制到应用程序空间。然后,流程只复制它感兴趣的传入流量的一部分

另一个应用程序示例:
有关示例,请参见

另一件需要注意的事情是,如果您使用所有这些连接与服务器通信(即,您是连接的客户端),则由于可用的本地端口数量,您的连接限制在65000个左右。不,您不是。每个客户端都连接到同一端口。顺便说一句,我认为这些线程没有硬限制?Paul Tuma在这里说的是:它们连接到服务器端的同一端口,但是客户端连接仍然需要在本地分配一个端口来接收来自服务器的响应。@FractalizeR:这是正确的。在当前的TCP实现中,每个传出连接都需要一个唯一的端口号。理论上,TCP/IP堆栈在分配传出端口时可能会注意到元组;实际上,这是不可能的,因为API:特别是bind()发生在connect()之前,无论是显式的还是隐式的。@Dobesvandermere由于我给出的原因,API不是“为它而存在的”。但是,如果你的应用程序是网络I/O绑定的,就像HTTP客户端O一样