Java中的NIO非阻塞编程

平时工作中用到的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结构图

  • NIO引入:分析普通Socket通信中存在的I/O问题——阻塞通信,并分析传统的解决方法——线程池的优缺点,进而引入NIO解决方案:
    ①基于Socket通信存在的问题:I/O阻塞通信。
    在介绍NIO之前,先了解传统I/O操作的方式。以网络应用为例,下图描述了一个典型的网络服务器结构的通信过程:
    传统网络IO操作过程
    椭圆形内的操作会循环进行,并且监听连接、读取数据、写入数据的操作都是阻塞的。在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类将传入客户机请求多路分用,并将它们分派到各自的请求处理程序,实现对客户端请求事件的非阻塞监听,如下图:
NIO通信过程
在椭圆形区域中,Selector监听器负责轮循客户端的连接、读取和写入事件,这些事件的执行都不会被阻塞。并且为了提高执行的效率,NIO在读取和写入的数据中使用了缓存区。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值