Java中BIO和NIO

同步/异步、阻塞/非阻塞概念

同步异步

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)

同步:在发出一个调用时,在没有得到结果之前,该调用就不返回。一旦调用返回,就得到返回值了。调用者主动等待这个调用的结果。
异步:调用在发出之后就直接返回了,没有立刻得到返回结果。在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

I/O

通常来说,IO操作包括:对硬盘的读写、对socket的读写以及外设的读写,并且需要进行用户空间和内核空
间的区分(用户空间就是普通的用户进程,内核空间就是内核进程,只有内核空间才可以直接范围磁盘等物理
I/O设备)

用户空间产生一个读请求,请求再转交由内核空间执行
1. 内核检查读取的数据是否就绪
2. 如果就绪,内核将数据从内核空间复制到用户空间(内存上拷贝)

阻塞I/O与非阻塞I/O

阻塞I/O:内核在检查数据未就绪时,会一直等待,直到数据就绪
非阻塞I/O:如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪

它们的区别在于I/O的第一阶段,阻塞是选择等待,非阻塞是返回一个标志信息

那么非阻塞I/O的优势在哪里呢?使用阻塞I/O处理网络连接时,有10000个连接就要开10000个线程,无论
有没有数据到来,处理某一连接的线程必须“忠实地阻塞”。而非阻塞I/O就不需要这样,它可以维护一个1000
个线程的线程池,当有数据就绪时,启动一个线程去接受数据,当没有数据时,线程不需要等待,直接就可以
回到池中,等待被调度到去接受其它连接。因此非阻塞I/O非常适合连接多但传输的数据内容不大的情况,
果连接少数据多,阻塞I/O更容易编程

同步I/O和异步I/O

事实上,同步IO和异步IO模型是针对用户线程和内核的交互来说的,即数据是否就绪的消息传递机制

同步IO:当用户发出IO请求操作之后,如果数据没有就绪,需要通过用户线程或者内核不断地去轮询数据是否
就绪,当数据就绪时,再将数据从内核拷贝到用户线程

异步IO:只有IO请求操作的发出是由用户线程来进行的,内核自动完成检查数据是否就绪和将数据拷贝
到用户空间的过程,然后发送通知告知用户线程IO操作已经完成。

BIO、NIO、AIO

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 

NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。 

AIO(NIO.2):  异步非阻塞IO,在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。

BIO

阻塞IO,一个请求对应一个线程,线程是阻塞的。

缺点:

阻塞式IO、弹性伸缩能力强、多线程消耗资源。

NIO

每个客户端请求都会注册到Selector(多路复用器)

优点:

非阻塞式IO模型、弹性伸缩能力强、单线陈节省资源。 

同步非阻塞式IO,关键是采用了事件驱动的思想实现了一个多路转换器。 

NIO与BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件,实现原理就是多路复用器,可以监听来自多个客户端的IO事件:
A. 若服务端监听到客户端连接请求,便为其建立通信套接字(java中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字。
B. 若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。
C. 监听多个客户端的连接请求和接收数据请求同时还能监听自己是否有数据要发送。


总之就是在一个线程中就可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应函数处理。 
一旦有请求到来(不管是几个同时到还是只有一个到),都会调用对应IO处理函数处理,所以:

(1)NIO适合处理连接数目特别多,但是连接比较短(轻操作)的场景Jetty,Mina,ZooKeeper等都是基于java nio实现

(2)BIO方式适用于连接数目比较小且固定的场景,这种方式对服务器资源要求比较高,并发局限于应用中。

Java的NIO代码

相关链接:java的socket编程(BIO)

this.selector.select(); 在这里会阻塞,无论是客户端连接还是客户端发送数据还是客户端关闭,这里都会触发。虽然这里是单线程,但是底层处理用到了线程池。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * 阻塞点this.selector.select();
 * 轮询器虽然是一个线程内部也是线程池
 */
public class NioSocket {
    private Selector selector;   //通道管理器(管理器)

    /**
     * 初始化Channel并绑定端口
     * @param port
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);  //非阻塞
        serverChannel.socket().bind(new InetSocketAddress(port));

        this.selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器已启动...");
    }

    /**
     * 监听轮询器
     * @throws IOException
     */
    public void listenSelector() throws IOException {
        //轮询监听selector
        while (true){
            //等待客户连接
            //select模型,多路复用
            //this.selector.select();   //在这里会阻塞,无论是连接还是客户端发送数据还是客户端关闭,这里都会触发
            this.selector.selectNow();   //这里不阻塞会立即执行
            Iterator<SelectionKey> iteKey = this.selector.selectedKeys().iterator();
            while (iteKey.hasNext()){
                SelectionKey key = iteKey.next();
                iteKey.remove();  //移除,防止重复处理
                //处理请求
                handler(key);
            }
        }
    }

    /**
     * 处理客户端请求
     * @param key
     */
    private void handler(SelectionKey key) throws IOException {
        if (key.isAcceptable()){  //处理连接请求
            //处理客户端连接请求事件
            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
            SocketChannel socketChannel = serverChannel.accept();
            //接受客户端发送的信息时,需要给通道设置读权限
            socketChannel.configureBlocking(false);
            socketChannel.register(selector,SelectionKey.OP_READ);
        }else if(key.isReadable()){   //处理读请求
            //处理读事件
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int readData = socketChannel.read(buffer);
            if (readData>0){
                String info  = new String(buffer.array(),"GBK").trim();
                System.out.println("服务端收到数据: "+Thread.currentThread()+info);
            }else {
                System.out.println("客户端关闭了...");
                key.cancel();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        NioSocket nio = new NioSocket();
        nio.initServer(8888);
        nio.listenSelector();
    }
}

 

参考

http://m.elecfans.com/article/632834.html

https://www.cnblogs.com/IUbanana/p/7245980.html

原文地址:https://www.cnblogs.com/aeolian/p/10773786.html