Java IO与NIO与任务队列的比较

6
我看到很多关于旧Java io模型和新的java nio模型的比较,前者是同步/阻塞的,后者是异步/非阻塞的。因为NIO是非阻塞的,所以在需要处理大量并发连接但不想分配大量线程(由于上下文切换/内存使用而无法扩展)时,它比java io更适合。
我对这个观点的问题是,将IO与NIO进行比较时,总是给出IO使用每个连接一个线程的示例。使用java IO的开发人员是否可以简单地为阻塞IO操作(例如文件读取或数据库查询)分配有限数量的线程(线程池)并将它们排队?假设我正在使用java的ServerSocket类从头开始构建http服务器。假设我从客户端收到需要进行数据库查询的请求,由于JDBC规范,这是一个阻塞操作。我不能简单地将数据库查询排队到ThreadPool中,并在池完成处理时提供回调来运行任务吗?当然,我正在分配线程来处理IO绑定请求,但线程的数量是有限的。由于IO操作通常是同步的(至少在某种程度上),尝试为每个数据库查询或文件读取/写入分配一个线程是没有意义的。
这样一来,您就可以获得线程和异步编程的好处,而无需分配过多的线程。
我认为这种模型唯一的弱点是,如果所有IO绑定操作都被卡住了(可能是由于编程错误),那么将来排队的请求将被搁置,直到它们解除卡住或超时。主要逻辑仍然是并发的,但IO不是。
所以问题是:NIO是否解决了上述模型无法解决的任何问题(除了我刚提到的潜在弱点)?
2个回答

4
当然,您可以使用池和有限数量的线程并处理自己的I/O,但这基本上是在复制NIO为您提供的功能,但无法利用本地API。
您的系统可能无法很好地处理成千上万个慢速I/O执行的套接字,例如处理服务器推送、BitTorrent客户端或非常繁忙的内容服务器所需的情况。
更传统的请求/响应系统将与您的解决方案配合良好,但再次,它们也将与每个请求一个线程的解决方案配合良好,因为通常遇到的第一个瓶颈将是CPU或内存消耗。

那么我认为NIO允许异步IO而无需使用线程。它使用JVM利用的操作系统级IO功能。使用我的模型,如果我为IO分配了N个线程,并且N个线程正在进行长期IO,则将来添加到队列中的小请求必须等待直到这些慢速连接(可能由于恶意客户端而变慢)完成。我理解得对吗? - Andrew Cahill
是的,我以前在共享一个大的线程池时遇到过类似的问题,其中包含慢速和快速任务。快速任务最终也会变得像慢速任务一样缓慢,因为所有慢速任务都会占用线程池。你的解决方案也可能会出现同样的情况。当然,这对于你的使用情况来说可能相当牵强,你的基础设施可能已经有防范措施来防止此类攻击。 - john16384
哈哈!我也想到过那个可能的解决方案!当然,那时使用NIO可能会更容易些。 - Andrew Cahill

1
如果您使用您所描述的模型创建了一个具有一些N个线程的Web服务器,那么恶意行为者可以打开N个连接并简单地不发送任何数据。现在您的服务器完全被锁定,无法执行有意义的工作。 NIO带来的是选择器,它允许您同时等待多个连接。使用此基元,您可以创建一个线程以等待所有当前连接。没有一个连接可以占据整个线程的执行状态,因为您只将该工作分配给单个线程。您系统中的所有其他线程现在都可以根据需要处理传入的数据。
即使是正常的、非恶意的参与者,也可能会破坏单线程/阻塞调用模型。您是正确的,IO操作可能会被卡住,并导致系统故障。然而,某些客户端(例如聊天会话中的持久客户端)可能会长时间处于空闲状态。并非对服务器的所有请求都是立即完成的。

任何非微不足道的服务器都会通过读取超时来防御这种攻击。 - user207421
当然,您可以设置SO_TIMEOUT。但是,由于片段存在,您无法使用它作为丢弃连接的机制。通过使用一些极小的读取超时(比如5毫秒),有人可以将1k字节的请求每次一字节地淌过来。你已经累计浪费了5秒钟的真实服务器时间,仅仅是在一个连接上。即使没有恶意用户,您也会因每次从套接字读取而产生上下文切换的开销和潜在的异常(因为这是Java表现超时的方式)。 - John H

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