您的位置:首页 > 编程语言 > Java开发

Java中的NIO非阻塞编程

2017-03-13 10:32 155 查看
平时工作中用到的IO主要是java.io包中的操作,比较少用到java.nio包中操作,最近遇到的比较多对性能要求较高的应用问题,查询了一些资料整理记录一下,方便以后查看。

在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在读取和写入的数据中使用了缓存区。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java api jdk nio