java nio为什么是非阻塞_Java nio都是非阻塞IO么?并非如此

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多路复用的概念:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值