如果有人再问你 Java IO,把这篇文章砸他头上( 四 )


如何提高网络 IO 传输效率、保证数据传输的可靠,已经成了工程师们急需解决的问题 。
6.4、IO 工作方式在计算机中,IO 传输数据有三种工作方式,分别是 BIO、NIO、AIO 。
在讲解 BIO、NIO、AIO 之前,我们先来回顾一下这几个概念:同步与异步,阻塞与非阻塞 。
同步与异步的区别

  • 同步就是发起一个请求后,接受者未处理完请求之前,不返回结果 。
  • 异步就是发起一个请求后,立刻得到接受者的回应表示已接收到请求,但是接受者并没有处理完,接受者通常依靠事件回调等机制来通知请求者其处理结果 。
阻塞和非阻塞的区别
  • 阻塞就是请求者发起一个请求,一直等待其请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续 。
  • 非阻塞就是请求者发起一个请求,不用一直等着结果返回,可以先去干其他事情,当条件就绪的时候,就自动回来 。
而我们要讲的 BIO、NIO、AIO 就是同步与异步、阻塞与非阻塞的组合 。
  • BIO:同步阻塞 IO;
  • NIO:同步非阻塞 IO;
  • AIO:异步非阻塞 IO;
6.4.1、BIOBIO 俗称同步阻塞 IO,一种非常传统的 IO 模型,比如我们上面所举的那个程序例子,就是一个典型的**同步阻塞 IO **的工作方式 。
如果有人再问你 Java IO,把这篇文章砸他头上

文章插图
 
采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接 。
我们一般在服务端通过while(true)循环中会调用accept()方法等待监听客户端的连接,一旦接收到一个连接请求,就可以建立通信套接字进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成,不过可以通过多线程来支持多个客户端的连接 。
客户端多线程操作,程序如下:
如果有人再问你 Java IO,把这篇文章砸他头上

文章插图
 
服务端多线程操作,程序如下:
如果有人再问你 Java IO,把这篇文章砸他头上

文章插图
 
服务端运行结果,如下:
如果有人再问你 Java IO,把这篇文章砸他头上

文章插图
 
如果要让 BIO 通信模型能够同时处理多个客户端请求,就必须使用多线程,也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁 。
这就是典型的一请求一应答通信模型。
如果出现 100、1000、甚至 10000 个用户同时访问服务器,这个时候,如果使用这种模型,那么服务端也会创建与之相同的线程数量,线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务 。
当然,我们可以通过使用 Java 中 ThreadPoolExecutor 线程池机制来改善,让线程的创建和回收成本相对较低,保证了系统有限的资源的控制,实现了 N (客户端请求数量)大于 M (处理客户端请求的线程数量)的伪异步 I/O 模型 。
6.4.2、伪异步 BIO为了解决同步阻塞 I/O 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数 M:线程池最大线程数 N 的比例关系,其中 M 可以远远大于 N,通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致资源耗尽 。
伪异步 IO 模型图,如下图:
如果有人再问你 Java IO,把这篇文章砸他头上

文章插图
 
采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,当有新的客户端接入时,将客户端的 Socket 封装成一个 Task 投递到后端的线程池中进行处理 。
Java 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理 。
客户端,程序如下:
如果有人再问你 Java IO,把这篇文章砸他头上

文章插图
 
服务端,程序如下:
如果有人再问你 Java IO,把这篇文章砸他头上

文章插图
 
先启动服务端程序,再启动客户端程序,看看运行结果!
服务端,运行结果如下:
如果有人再问你 Java IO,把这篇文章砸他头上

文章插图
 


推荐阅读