美文网首页
Java nio都是非阻塞IO么?并非如此

Java nio都是非阻塞IO么?并非如此

作者: 业松 | 来源:发表于2021-01-23 21:41 被阅读0次

    java中的nio包,对于java程序员来说是个熟悉又陌生的东西。以前一直以为nio=Non-blocking I/O,即非阻塞IO。后来又听人说nio其实是new IO新一代IO的意思。两种说法到底哪种是正确的?我去Oracle的java官网查看doc,很遗憾也没直接解释nio是什么单词的缩写。但是经过一番实践,确证new IO才是nio正确的全称。

    一段代码引发的思考

        ByteBuffer buf = ByteBuffer.allocate(48);
        buf.clear();
    
        channelA.read(buf);
    
        buf.flip();
    
        while(buf.hasRemaining()) {
            channelB.write(buf);
        }
    

    这是在网上搜FileChannel看到的一段代码。这个时候我以为nio下的一切io操作都是非阻塞的,于是看到这段代码我就非常费解。
    为什么FileChannel在read的时候没有加while判断,在write的时候就要加while判断了?难道说对FileChannel来说read是线程阻塞操作,write是非阻塞的?这就有点匪夷所思了。看到doc文档也没找到答案。

    于是我自己试验了一下,在while循环里添加日志,如果write是非阻塞的,那么while应该打印很多次。最后的结果是while里只打印一次日志,代表read和write都是阻塞的!
    这时候其实我更迷惑了,因为我一直以为nio下的io操作都是非阻塞的。
    于是再次去百度FileChannel到底是阻塞还是非阻塞的。找了很久没找到确切的答案,最后在StackOverflow上找到了一个比较满意的回答:

    UNIX does not support non-blocking I/O for files, see Non-blocking I/O with regular files. As Java should (at least try to) provide the same behaviour on all platforms, the FileChannel does not implement SelectableChannel.
    UNIX不支持文件的非阻塞IO,参看这个网页的说明。由于Java在全平台上要保持行为一致(或者努力这么做),所以FileChannel没有实现SelectableChannel。

    However Java 7 will include a new AsynchronousFileChannel class that supports asynchronous file I/O, which is a different mechanism to non-blocking I/O.
    但是Java7上引入了一个支持异步文件IO的AsynchronousFileChannel类,但是这是跟非阻塞IO不一样的机制。

    In general only sockets and pipes truly support non-blocking I/O via select() mechanism.
    一般来说只有socktes和pipes通过select机制真正支持非阻塞IO。

    从这段话来说,首先FileChannel肯定是阻塞IO的;其次实现了SelectableChannel接口才能实现非阻塞IO。从FileChannel上也能看出来,nio下不是所有io操作都是非阻塞的。因此nio的全称绝不应该是non-blocking I/O,而应该是new IO。
    当然这里还说了异步和非阻塞是完全不一样的机制,这个以后再来了解吧。

    Channels&Buffers

    nio下有很多类,对我们来说以下三个是最重要的:

    • Channels
    • Buffers
    • Selectors
      第一代io使用字节流和字符流来进行读写,而nio使用Channels和Buffers来实现读写。数据总是从Channel读到buffer里,再从buffer写到Channel里。这个是使用上很大的区别。
      Channel和字符字节流有点类似,但是Channel都是双向的,既可以读,也可以写。
      以前的io流我们一般直接定义一个byte数组来作为buffer,但是nio里需要使用Buffer类。
      下面是网上找的一个使用FileChannel和Buffer的例子:
        RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
        FileChannel inChannel = aFile.getChannel();
    
        //创建48个字节长度的ByteBuffer
        ByteBuffer buf = ByteBuffer.allocate(48);
    
        int bytesRead = inChannel.read(buf); //从Channel中读取数据到buffer里
        while (bytesRead != -1) {
    
        buf.flip();  //切换buffer到读取状态
    
        while(buf.hasRemaining()){
            System.out.print((char) buf.get()); //每次读取一个字节
        }
    
        buf.clear(); //清空buffer后才能继续从Channel里读取数据
        bytesRead = inChannel.read(buf);
        
        aFile.close();
    

    Buffer实例化的时候需要传入长度。在buffer写好数据,准备读取到Channel里的时候,一定要执行flip操作,buffer才可以读取。同样的,再次写入数据之前,buffer需要clear一下清除数据。

    Selector和非阻塞IO

    但是nio最吸引我们的肯定还是非阻塞IO,而java nio里非阻塞IO的实现要靠Selector。Selector是一种IO多路复用机制的实现。简单来说,就是用单个线程/进程来对多个IO Channel进行轮询。内核不管IO有没有完成都会立即返回给用户,这样单个IO的阻塞就不会阻塞用户线程。单个线程无需一个个等待IO完成再工作,而是发现有完成的就处理,处理完继续轮询,直到下一个完成的IO出现。

    这样做的好处就是单个线程即可处理海量并发请求,当然前提是业务的数据处理不能太耗时,否则线程会卡在数据处理上。NodeJs就是单线程非阻塞IO模型,因此擅长处理高并发。同样适合非阻塞IO的还有nginx、redis等高并发但是计算简单的软件。而Java数据库IO连接的JDBC是阻塞io,因此Java服务器不太适合nio,而适合多线程来处理并发。

    对Android程序员来说,Looper中的MessgeQueue在执行next方法获取下一条handler消息的时候,如果没有消息,主线程执行nativePollOnce方法阻塞住。这样主线程在没事的时候就不会消耗cpu资源。在有消息传过来时,android framework除了把消息添加到MessageQueue,还会把主线程给wakeup。那么怎么样block(阻塞)线程和wakeup(唤醒)线程呢?通过linux系统的epoll机制,而epoll机制就是selector相对的poll机制的加强版。

    以上只是我的一点形而上的理解,有可能存在谬误,推荐几个解释的好的博文来学习Selector以及IO多路复用的概念:

    Java NIO 系列教程(翻译的国外教程)

    聊聊Linux 五种IO模型

    StackOverflow上关于NativePollOnce方法的讨论

    相关文章

      网友评论

          本文标题:Java nio都是非阻塞IO么?并非如此

          本文链接:https://www.haomeiwen.com/subject/fcjuzktx.html