在JDK1.4以前,Java的IO操作集中在java.io这个包中,是基于流的阻塞API。对于大多数应用来说,这样的API使用很方便,然而,一些对性能要求较高的应用,尤其是服务端应用,往往需要一个更为有效的方式来处理IO。从JDK1.5开始,NIO API作为一个基于缓存区,并能提供非阻塞IO操作的API被引入。
NIO所在的包为java.nio,其中的n表示non-blocking。但实际上我们可以把它理解为nio=new+io,因为NIO包实现了网络通信和I/O的联合功能,并将它们的结合发挥到极致,实现完美的网络非阻塞通信功能。
-
NIO引入:分析普通Socket通信中存在的I/O问题——阻塞通信,并分析传统的解决方法——线程池的优缺点,进而引入NIO解决方案:
①基于Socket通信存在的问题:I/O阻塞通信。
在介绍NIO之前,先了解传统I/O操作的方式。以网络应用为例,下图描述了一个典型的网络服务器结构的通信过程:
椭圆形内的操作会循环进行,并且监听连接、读取数据、写入数据的操作都是阻塞的。在ServerSocket类的生存期中,其重要功能如下: -
首先创建ServerSocket:
//启动服务端:ServerSocket server= new ServerSocket(12345);
- 然后接受新的连接请求:
//监听客户端while(true){Socket socket = server.accept(); //阻塞监听}
对accept()的调用将一直阻塞,直到ServerSocket接受到一个连接请求为止。一旦请求连接被接受,服务器可以读取客户Socket中的输入/输出数据,在读取调用时也会阻塞。下面的代码演示了这个过程:
//输入输出流
BufferReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter os = new PrintWriter(socket.getOutputStream());
//读取数据
String line;//垃圾字符串
while((line=is.readLine())!=null){//读阻塞//回复数据os.println(line);//写阻塞os.flush();
}
在监听的位置accept()会被阻塞,并且在读写客户端数据时也会阻塞。因此这样的操作共造成了3个问题:
a、accept()方法的调用将造成阻塞,直到ServerSocket接受到一个连接请求位置;
b、BufferedReader类的readLine()方法在其缓存区未满时将会造成线程阻塞,只有数据足够填满缓存区或者客户端关闭了套接字时,方法才会返回。
c、产生大量的String垃圾,BufferedReader创建了缓存区从客户套接字读入数据,但是同样创建了一些字符串存储这些数据。虽然BufferedReader内部提供了StringBuffer处理,但是所有的String很快变成了垃圾需要回收,同样的问题在发送消息代码中也存在。
其中第一个问题是ServerSocket造成的阻塞,第二个问题是BufferedReader缓存造成的阻塞,第三个问题是String造成的垃圾。因此,以上的问题是Java I/O和Java网络通信共同造成的。
②传统的解决方法:使用线程池。
在JDK 1.4之前,自由使用线程池是处理阻塞问题最典型的办法。面对大量的客户端的请求,需要使用大量的线程,这时一般是实现一个线程池来处理请求,如图:
使用线程池的方法是:在服务端启动时创建线程池,当监听到客户端连接时,就为客户端创建一个线程,并将该线程放入线程池中即可。这样在该客户断开连接时,该客户端的处理线程就会被归还到线程池中,以提高线程的池化管理,提高线程的使用效率。实例代码如下:
public class TestThreadPool{public static void main(String args[]){boolean flag = true;try{//创建线程池ExecutorService pool = Executors.newFixedThreadPool(10);//启动服务器ServerSocket server = new ServerSocket(12345);System.out.println("开始监听");while(true){//接受客户端连接Socket socket = server.accept();//为客户端创建一个独立线程pool.execute(new ServiceThread(socket));}//关闭server.close();pool.shutdown();}catch(IoException e){e.printStackTrace();}}
}
线程池使服务器可以处理多个连接,但是它们也同样引发了许多问题。每个线程拥有自己的栈空间并且占用一些CPU时间,耗费很大,而且很多时间是浪费在阻塞的IO操作上,没有有效利用CPU。
③最新的解决方案:NIO非阻塞通信。
从上面的分析可以看出,采用线程池的解决方法也会产生它自己的问题——线程开销,线程开销同时也影响性能和可伸缩性。不过,随着NIO的到来,一切都改变了。
NIO的非阻塞IO机制是围绕选择器和通道构建的。Channel类表示服务器和客户机直接的一种通信机制。与反应器模式一致,Selector类是Channel的多路复用器。Selector类将传入客户机请求多路分用,并将它们分派到各自的请求处理程序,实现对客户端请求事件的非阻塞监听,如下图:
在椭圆形区域中,Selector监听器负责轮循客户端的连接、读取和写入事件,这些事件的执行都不会被阻塞。并且为了提高执行的效率,NIO在读取和写入的数据中使用了缓存区。
转发:https://blog.csdn.net/luliuliu1234/article/details/61914097