java中的nio包,對(duì)于java程序員來(lái)說(shuō)是個(gè)熟悉又陌生的東西症汹。以前一直以為nio=Non-blocking I/O硫朦,即非阻塞IO。后來(lái)又聽(tīng)人說(shuō)nio其實(shí)是new IO新一代IO的意思背镇。兩種說(shuō)法到底哪種是正確的咬展?我去Oracle的java官網(wǎng)查看doc,很遺憾也沒(méi)直接解釋nio是什么單詞的縮寫瞒斩。但是經(jīng)過(guò)一番實(shí)踐破婆,確證new IO才是nio正確的全稱。
一段代碼引發(fā)的思考
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channelA.read(buf);
buf.flip();
while(buf.hasRemaining()) {
channelB.write(buf);
}
這是在網(wǎng)上搜FileChannel看到的一段代碼胸囱。這個(gè)時(shí)候我以為nio下的一切io操作都是非阻塞的祷舀,于是看到這段代碼我就非常費(fèi)解。
為什么FileChannel在read的時(shí)候沒(méi)有加while判斷,在write的時(shí)候就要加while判斷了裳扯?難道說(shuō)對(duì)FileChannel來(lái)說(shuō)read是線程阻塞操作抛丽,write是非阻塞的?這就有點(diǎn)匪夷所思了饰豺∫谙剩看到doc文檔也沒(méi)找到答案。
于是我自己試驗(yàn)了一下哟忍,在while循環(huán)里添加日志狡门,如果write是非阻塞的,那么while應(yīng)該打印很多次锅很。最后的結(jié)果是while里只打印一次日志其馏,代表read和write都是阻塞的!
這時(shí)候其實(shí)我更迷惑了爆安,因?yàn)槲乙恢币詾閚io下的io操作都是非阻塞的叛复。
于是再次去百度FileChannel到底是阻塞還是非阻塞的。找了很久沒(méi)找到確切的答案扔仓,最后在StackOverflow上找到了一個(gè)比較滿意的回答:
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褐奥,參看這個(gè)網(wǎng)頁(yè)的說(shuō)明。由于Java在全平臺(tái)上要保持行為一致(或者努力這么做)翘簇,所以FileChannel沒(méi)有實(shí)現(xiàn)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上引入了一個(gè)支持異步文件IO的AsynchronousFileChannel類,但是這是跟非阻塞IO不一樣的機(jī)制版保。
In general only sockets and pipes truly support non-blocking I/O via select() mechanism.
一般來(lái)說(shuō)只有socktes和pipes通過(guò)select機(jī)制真正支持非阻塞IO呜笑。
從這段話來(lái)說(shuō),首先FileChannel肯定是阻塞IO的彻犁;其次實(shí)現(xiàn)了SelectableChannel接口才能實(shí)現(xiàn)非阻塞IO叫胁。從FileChannel上也能看出來(lái),nio下不是所有io操作都是非阻塞的汞幢。因此nio的全稱絕不應(yīng)該是non-blocking I/O驼鹅,而應(yīng)該是new IO。
當(dāng)然這里還說(shuō)了異步和非阻塞是完全不一樣的機(jī)制森篷,這個(gè)以后再來(lái)了解吧输钩。
Channels&Buffers
nio下有很多類,對(duì)我們來(lái)說(shuō)以下三個(gè)是最重要的:
- Channels
- Buffers
- Selectors
第一代io使用字節(jié)流和字符流來(lái)進(jìn)行讀寫疾宏,而nio使用Channels和Buffers來(lái)實(shí)現(xiàn)讀寫张足。數(shù)據(jù)總是從Channel讀到buffer里,再?gòu)腷uffer寫到Channel里坎藐。這個(gè)是使用上很大的區(qū)別。
Channel和字符字節(jié)流有點(diǎn)類似,但是Channel都是雙向的岩馍,既可以讀碉咆,也可以寫。
以前的io流我們一般直接定義一個(gè)byte數(shù)組來(lái)作為buffer蛀恩,但是nio里需要使用Buffer類疫铜。
下面是網(wǎng)上找的一個(gè)使用FileChannel和Buffer的例子:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//創(chuàng)建48個(gè)字節(jié)長(zhǎng)度的ByteBuffer
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //從Channel中讀取數(shù)據(jù)到buffer里
while (bytesRead != -1) {
buf.flip(); //切換buffer到讀取狀態(tài)
while(buf.hasRemaining()){
System.out.print((char) buf.get()); //每次讀取一個(gè)字節(jié)
}
buf.clear(); //清空buffer后才能繼續(xù)從Channel里讀取數(shù)據(jù)
bytesRead = inChannel.read(buf);
aFile.close();
Buffer實(shí)例化的時(shí)候需要傳入長(zhǎng)度。在buffer寫好數(shù)據(jù)双谆,準(zhǔn)備讀取到Channel里的時(shí)候壳咕,一定要執(zhí)行flip操作,buffer才可以讀取顽馋。同樣的谓厘,再次寫入數(shù)據(jù)之前,buffer需要clear一下清除數(shù)據(jù)寸谜。
Selector和非阻塞IO
但是nio最吸引我們的肯定還是非阻塞IO竟稳,而java nio里非阻塞IO的實(shí)現(xiàn)要靠Selector。Selector是一種IO多路復(fù)用機(jī)制的實(shí)現(xiàn)熊痴。簡(jiǎn)單來(lái)說(shuō)他爸,就是用單個(gè)線程/進(jìn)程來(lái)對(duì)多個(gè)IO Channel進(jìn)行輪詢。內(nèi)核不管IO有沒(méi)有完成都會(huì)立即返回給用戶果善,這樣單個(gè)IO的阻塞就不會(huì)阻塞用戶線程诊笤。單個(gè)線程無(wú)需一個(gè)個(gè)等待IO完成再工作,而是發(fā)現(xiàn)有完成的就處理巾陕,處理完繼續(xù)輪詢讨跟,直到下一個(gè)完成的IO出現(xiàn)。
這樣做的好處就是單個(gè)線程即可處理海量并發(fā)請(qǐng)求惜论,當(dāng)然前提是業(yè)務(wù)的數(shù)據(jù)處理不能太耗時(shí)许赃,否則線程會(huì)卡在數(shù)據(jù)處理上。NodeJs就是單線程非阻塞IO模型馆类,因此擅長(zhǎng)處理高并發(fā)混聊。同樣適合非阻塞IO的還有nginx、redis等高并發(fā)但是計(jì)算簡(jiǎn)單的軟件乾巧。而Java數(shù)據(jù)庫(kù)IO連接的JDBC是阻塞io句喜,因此Java服務(wù)器不太適合nio,而適合多線程來(lái)處理并發(fā)沟于。
對(duì)Android程序員來(lái)說(shuō)咳胃,Looper中的MessgeQueue在執(zhí)行next方法獲取下一條handler消息的時(shí)候,如果沒(méi)有消息旷太,主線程執(zhí)行nativePollOnce方法阻塞住展懈。這樣主線程在沒(méi)事的時(shí)候就不會(huì)消耗cpu資源销睁。在有消息傳過(guò)來(lái)時(shí),android framework除了把消息添加到MessageQueue存崖,還會(huì)把主線程給wakeup冻记。那么怎么樣block(阻塞)線程和wakeup(喚醒)線程呢?通過(guò)linux系統(tǒng)的epoll機(jī)制来惧,而epoll機(jī)制就是selector相對(duì)的poll機(jī)制的加強(qiáng)版冗栗。
以上只是我的一點(diǎn)形而上的理解,有可能存在謬誤供搀,推薦幾個(gè)解釋的好的博文來(lái)學(xué)習(xí)Selector以及IO多路復(fù)用的概念: