作者:V7
博客:https://www.jvmstack.cn
一碗鸡汤
少年辛苦终身事,莫向光阴惰寸功。 —— 杜荀鹤
同步阻塞IO
在介绍阻塞和非阻塞之前先说明一下同步和异步。我们可以将同步和异步看做是发起IO请求的两种方式。同步IO指的是用户空间(进程或者线程)是主动发起IO请求的一方,系统内核是被动接受方。异步IO则反之,系统内核是主动发起IO请求的一方,而用户空间是被动接受方。
在这里先介绍阻塞和非阻塞。阻塞IO指的是需要内核空间彻底完成IO操作之后返回到用户空间再执行用户程序的操作指令。这里的阻塞指的是用户程序(发起IO请求的进程或线程)的执行状态。我们平常所说的传统的IO都是阻塞IO模型,在Java网络编程中的socket都属于阻塞IO模型。
那么同步阻塞IO指的就是用户空间主动发起,需要等待内核IO操作彻底完成之后才返回到用户空间的IO操作,在IO操作的过程中,发起IO请求的用户进程或者线程处于阻塞状态。
关于用户空间和内核空间的知识点不清楚的可以查看前面网络编程基础
的内容。
同步阻塞IO流程
默认情况下,在Java应用程序中对socket的所有连接进行的IO操作都是同步阻塞IO。在阻塞IO模型中,从Java应用程序发起IO系统调用开始,一直到系统调用返回,这个时间段内发起IO请求的Java进程或者线程是阻塞的。同步阻塞IO的流程如下所示:
阻塞IO的特点是在内核执行IO操作的两个阶段,发起IO请求的用户进程或线程被阻塞了。
阻塞IO的优点是:应用程序开发简单,在阻塞等待数据时间段,用户线程是挂起的,基本不会占用CPU资源。
阻塞IO的缺点是:一般情况下会为每个连接创建一个独立的线程,一个线程维护一个连接的IO操作。这种方式在并发量小的时候一般不会有什么问题。但是在高并发的应用场景下,阻塞IO模型需要大量的线程来维护大量的网络连接,内存、线程上下文切换的开销是非常巨大的,导致性能非常低下,所以在高并发的场景下基本上是不可用的。
BIO的通信模型图
网络编程的基本模型是client/Server模型,也就是我们常说的C/S通信模型,它们是两个进程之间的通信。其中服务器端提供绑定的IP和监听端口这种位置信息,客户端通过连接操作向服务端监听的地址发起连接请求,通过TCP的三次握手建立连接,如果连接建立成功,客户端和服务端就可以通过网络套接字(socket)进行通信。
在基于传统同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口。Socket负责发起连接操作。双方连接从成功后,通过输入和输出流进行同步阻塞式通信。
BIO的服务端通信模型的服务端通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后通过输出流返回应答给客户端,线程销毁。这个就是典型的一请求一应答通信模型。
通过上图可以看出,该模型的最大问题就是缺乏弹性伸缩能力,假设当客户端并发访问量增加之后,服务端新创建的线程数量和客户端发起请求的数量是1:1的正比关系,在Java中线程是虚拟机非常宝贵的资源,当线程的数量膨胀之后,系统的性能将急剧下降。最坏的结果就是当并发访问量持续增大,系统会出现线程堆栈溢出、创建新线程失败、客户端连接失败等问题,最终导致进程宕机或僵死以致系统不能对外提供服务。
总结
本章是对Java中传统IO编程模型的解读,在实际工作中可以根据实际情况选择对该模型的应用。
扫码关注编程之艺术
一起学习、进步。