深入理解Java NIO

所谓NIO,即new I/O,在JDK1.4用于改善原来I/O中的不足,通过进一步减少I/O操作中阻塞的粒度来提高I/O效率,所以也被称为NonBlocking I/O,非阻塞I/O

一、IO与NIO的区别

要说两种I/O的区别,其实它是被划分进不同的I/O模型中的,所以我们就先来看一下五种I/O模型的区别

五种I/O模型:

1.1 阻塞I/O模型

在用户进程(线程)中调用执行的时候,进程会等待该IO操作,而使得这个进程的其他操作无法执行。

举个例子,假如现在有一个socket需要读取通过网络传输获得的一段信息,当这个socket调用read()操作后,因为种种原因这段信息可能没有被接收或者说没有接收完全,而此时由于这个线程阻塞而被挂起,直到整个IO操作全部完成才能返回线程。

可见,这个模型在内存IO或者是网络IO中的效率都是很低的;另外,在并发量较大的情况下,也就会因为线程容易挂起而创建更多的线程,进而导致对于线程创建销毁的开销过大而影响整体性能。但是,它也有仅存的一点好处:该模型挂起时不消耗CPU资源,并且能够及时响应每个操作

应用:阻塞socket、Java BIO(blocking IO)。

1.2 非阻塞IO模型

在用户进程中调用执行的时候,无论成功与否,该IO操作会立即返回(若缓冲区没有数据,则抛出异常然后返回),之后进程可以进行其他操作。

举个例子,仍然是一个socket需要读取通过网络传输获得的一段信息,当这个socket调用read()操作后,若缓冲区有数据,则直接读取;若缓冲区没有数据,则抛出异常返回。然后,这个线程会持续read()方法,检查缓冲区是否有新的数据进入,直到完成整个IO操作。

这个模型相比于之前效率有一定提高,但是暂时没有完成IO操作的线程持续轮询调用,会消耗CPU资源,降低CPU吞吐量。

应用:socket的同步非阻塞IO。

1.3 IO复用模型

这个模型中的每个socket会注册到一个selector上,统一监听IO操作,然后分出一个线程调用这个select()方法,这样就实现了从一线程一IO到一个线程来管理多个连接的转变。另外,值得说明的是,该模型中的selector实际上是实现了观察者模式,用一个selector来统一接收IO的信息,并且返回给相应的socket。但需要注意的是,该模型是一个仍然异步阻塞IO操作,是因为select()方法是阻塞的,或者也可以理解一种阻塞粒度更细的IO操作。
应用:Java NIO

1.4 IO信号驱动模型

当一个进程中的socket发起一个IO操作,会向内核注册一个信号处理函数,并且进程返回不阻塞;当这个信号处理函数返回时,内核会向进程发送一个信号量,以便通知进程调用IO读取数据,换句话说就是通过一个信号量来实现一个回调机制,提高CPU的吞吐量。

1.5 异步IO模型

该模型应用于Java 7中AIO,会在后续的文章中加以详细介绍。

二、NIO中关键接口

2.1 Class SocketChannel

即为面向流的socket通道,或者可以理解为Socket的一个完善类,除了可以提供Socket的功能外,还提供了更多的特性。

2.1.1 public abstract Socket socket()

可以返回一个与Channel关联的Socket,进而实现Socket类的功能。
在这里插入图片描述
因为该方法本身是抽象的,所以在SocketChannelImpl实现类中实现了这个方法,如上。

2.1.2 public static SocketChannel open() throws IOException

打开套接字通道,通过调用系统范围默认的SelectorProvider对象的openSocketChannel方法创建新Channel;若不能创建,则抛出IOException异常。

2.1.3 public final SelectableChannel configureBlocking(boolean) throws IOException

比较重要的一个方法,设置为false,则在该方法后的Socket则是非阻塞的,否则,仍然阻塞。
另外,这个方法是AbstractSelectableChannel类中的final方法,而SocketChannel则是这个抽象类的子类。

2.1.4 public final SelectionKey register(Selector , int ) throws ClosedChannelException

向Selector中注册,其中参数int表示监听事件的类型,如下:
在这里插入图片描述
这样的好处是,selector 不会去遍历所有关联的 socket,而是只会是那些符合事件类型,并且完成就绪操作的 socket,减少了大量无效的遍历操作。

2.2 Class Selector

SelectableChannel类的多路复用器,每个SelectableChannel类(上文中SocketChannel的基类)可向复用器中注册,然后由Selector统一监听IO操作。

2.2.1 public abstract int select() throws IOException

返回Selector中注册的SelectableChannel的数量。

2.3 SelectionKey

上文中register()返回值即为SelectionKey,用作已注册在Selector中的Channel的指针。

//遍历Selector中的Channel
while(selector.select()>0){
	Set keys=selector.selectedKeys();
	Integer it=keys.iterator();
	while (it.hasNext()){
		SelectionKey key = (SelectionKey)it.next();
		it.remove();
        if (key.isConnectable())
        	connect(key);
        else if (key.isWritable())
        	write(key);
        else if (key.isReadable())
        	receive(key);
    }
}

三、NIO的阻塞实现

完整代码如下:

public static class HttpConstant{
    private static String[] HOSTS;
    private static Integer PORT;
}

用于存放需要访问的服务器域名以及端口号。

public class NioBlockingClient{
	private SocketChannel socketChannel;
    private String host; 
    public static void main(String[] args) throws IOException {
        for(String host:HttpConstant.HOSTS) {
            NioBlockingHttpClient client=new NioBlockingHttpClient(host,HttpConstant.PORT);
            client.request();
        }
    }
}
private void request() throws IOException{
        PrintWriter writer=new PrintWriter(socketChannel.socket().getOutputStream());//装饰者模式
        BufferedReader reader=new BufferedReader(new InputStreamReader(
                                                    socketChannel.socket().getInputStream()));
        writer.write(host);
        writer.flush();
        String msg;
    while((msg=reader.readLine())!=null)
        System.out.println(msg);
    }

四、NIO的非阻塞实现

public class NioNonBlockingClient{

	private static Selector selector;
    private Charset charset=Charset.forName("utf-8");
    
    static{
        try{
            selector=Selector.open();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws IOException{
        NioBlockingHttpClient client=new NioBlockingHttpClient();
        for(String host:HOSTS)
            client.request(host,PORT);
        client.select();
    }
    public void request(String host,int port) throws IOException{
        SocketChannel socketChannel=SocketChannel.open();
        socketChannel.socket().setSoTimeout(5000);
        SocketAddress remote=new InetSocketAddress(host,port);
        socketChannel.configureBlocking(false);
        socketChannel.connect(remote);
        socketChannel.register(selector,SelectionKey.OP_READ);
    }
    public void select(){
        while(selector.select(500)>0){
            Set keys=selector.selectedKeys();
            Iterator it=keys.iterator();
            while(selector.select()>0){
				Set keys=selector.selectedKeys();
				Integer it=keys.iterator();
				while (it.hasNext()){
					SelectionKey key = (SelectionKey)it.next();
					it.remove();
       				 if (key.isConnectable())
        				connect(key);
        			else if (key.isWritable())
        				write(key);
       				 else if (key.isReadable())
        				receive(key);
    			}
			}
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值