Java之NIO深入淺出

相關(guān)問題

IO奠滑、BIO丹皱、NIO、AIO從java含義代表是什么宋税?
NIO可以對文件操作嗎摊崭?
BIO有什么缺陷?
NIO為了解決什么問題杰赛?
NIO有哪些核心組件以及核心組件內(nèi)容呢簸?

linux網(wǎng)絡(luò)IO模型有哪些?
NIO-零拷貝是否了解乏屯,javaNIO中零拷貝到底省去了那一層級的內(nèi)存copy?
NIO-epoll機(jī)制是否了解根时?
NIO用到了那個經(jīng)典技術(shù)思想?

基本概念

image.png

同步辰晕、異步與阻塞蛤迎、非阻塞

同步與異步

同步: 同步就是發(fā)起一個調(diào)用后,被調(diào)用者未處理完請求之前含友,調(diào)用不返回替裆。
異步: 異步就是發(fā)起一個調(diào)用后校辩,立刻得到被調(diào)用者的回應(yīng)表示已接收到請求,但是被調(diào)用者并沒有返回結(jié)果,此時我們可以處理其他的請求,被調(diào)用者通常依靠事件冰蘑,回調(diào)等機(jī)制來通知調(diào)用者其返回結(jié)果厌秒。
同步和異步的區(qū)別最大在于異步的話調(diào)用者不需要等待處理結(jié)果,被調(diào)用者會通過回調(diào)等機(jī)制來通知調(diào)用者其返回結(jié)果汉形。

阻塞和非阻塞

阻塞: 阻塞就是發(fā)起一個請求纸镊,調(diào)用者一直等待請求結(jié)果返回,也就是當(dāng)前線程會被掛起概疆,無法從事其他任務(wù)逗威,只有當(dāng)條件就緒才能繼續(xù)。
非阻塞: 非阻塞就是發(fā)起一個請求岔冀,調(diào)用者不用一直等著結(jié)果返回凯旭,可以先去干其他事情。

IO讀寫原理

無論是Socket的讀寫還是文件的讀寫使套,在Java層面的應(yīng)用開發(fā)或者是linux系統(tǒng)底層開發(fā)罐呼,都屬于輸入input和輸出output的處理,簡稱為IO讀寫侦高。
先強(qiáng)調(diào)一個基礎(chǔ)知識:read嫉柴、write調(diào)用,并不是物理設(shè)備與內(nèi)存的直接調(diào)用奉呛。
read系統(tǒng)調(diào)用计螺,是把數(shù)據(jù)從內(nèi)核空間的內(nèi)核緩沖區(qū)復(fù)制到進(jìn)程空間進(jìn)程緩沖區(qū);而write系統(tǒng)調(diào)用瞧壮,是把數(shù)據(jù)從進(jìn)程空間的進(jìn)程緩沖區(qū)復(fù)制到內(nèi)核空間的內(nèi)核緩沖區(qū)登馒。這個兩個系統(tǒng)調(diào)用,都不負(fù)責(zé)數(shù)據(jù)在內(nèi)核緩沖區(qū)和磁盤之間的交換咆槽。底層的讀寫交換陈轿,是由操作系統(tǒng)kernel內(nèi)核完成的。


image.png

內(nèi)核空間[Kernel space]和用戶空間[User space]

內(nèi)核空間是Linux內(nèi)核運行的空間罗晕,而用戶空間是用戶程序的運行空間济欢,為了保證內(nèi)核安全,它們之間是隔離的小渊,即使用戶的程序崩潰了法褥,內(nèi)核也不受影響。
在 CPU 的所有指令中酬屉,有些指令是非常危險的半等,如果錯用揍愁,將導(dǎo)致系統(tǒng)崩潰,比如清內(nèi)存杀饵、設(shè)置時鐘等莽囤。如果允許所有的程序都可以使用這些指令,那么系統(tǒng)崩潰的概率將大大增加切距。所以linux為了保證安全朽缎,設(shè)立了以上兩個空間。
用戶進(jìn)程通過系統(tǒng)調(diào)用訪問系統(tǒng)資源的時候谜悟,需要切換到內(nèi)核態(tài)话肖,而這對應(yīng)一些特殊的堆棧和內(nèi)存環(huán)境,必須在系統(tǒng)調(diào)用前建立好葡幸。而在系統(tǒng)調(diào)用結(jié)束后最筒,cpu會從內(nèi)核態(tài)切回到用戶態(tài),而堆棧又必須恢復(fù)成用戶進(jìn)程的上下文蔚叨。而這種切換就會有大量的耗時床蜘。
當(dāng)進(jìn)程運行在內(nèi)核空間時就處于內(nèi)核態(tài),當(dāng)進(jìn)程運行在用戶空間時就處于用戶態(tài)蔑水。

內(nèi)核緩沖區(qū)和用戶緩存區(qū)

緩沖區(qū)的目的邢锯,是為了減少頻繁的系統(tǒng)IO調(diào)用。大家都知道搀别,系統(tǒng)調(diào)用需要保存之前的進(jìn)程數(shù)據(jù)和狀態(tài)等信息弹囚,而結(jié)束調(diào)用之后回來還需要恢復(fù)之前的信息,為了減少這種損耗時間领曼、也損耗性能的系統(tǒng)調(diào)用鸥鹉,于是出現(xiàn)了緩沖區(qū)。
有了緩沖區(qū)庶骄,操作系統(tǒng)使用read函數(shù)把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到進(jìn)程緩沖區(qū)毁渗,write把數(shù)據(jù)從進(jìn)程緩沖區(qū)復(fù)制到內(nèi)核緩沖區(qū)中。等待緩沖區(qū)達(dá)到一定數(shù)量的時候单刁,再進(jìn)行IO的調(diào)用灸异,提升性能。至于什么時候讀取和存儲則由內(nèi)核來決定羔飞,用戶程序不需要關(guān)心。
在linux系統(tǒng)中么伯,系統(tǒng)內(nèi)核也有個緩沖區(qū)叫做內(nèi)核緩沖區(qū)田柔。每個進(jìn)程有自己獨立的緩沖區(qū),叫做進(jìn)程緩沖區(qū)欣舵。
所以缘圈,用戶程序的IO讀寫程序准验,大多數(shù)情況下,并沒有進(jìn)行實際的IO操作颠黎,而是在讀寫自己的進(jìn)程緩沖區(qū)狭归。

Unix網(wǎng)絡(luò)編程中有五種IO模型

blocking IO(阻塞IO):傳統(tǒng)IO模型               
nonblocking IO(非阻塞IO):默認(rèn)創(chuàng)建的socket都是阻塞的过椎,同步非阻塞IO要求socket被置為NONBLOCK.[這是的NIO并非是java的NIO(new IO)]               
IO multiplexing(多路復(fù)用IO):也稱為同步【它是在內(nèi)核態(tài)輪詢所有socket,偽異步】阻塞IO【阻塞多個socketIO請求】赏殃,Java的Selector和linux中的epoll都是這種模型仁热。即經(jīng)典的reactor設(shè)計模式抗蠢。               
signal driver IO(信號驅(qū)動IO)               
asynchronous IO (異步IO):即經(jīng)典的Proactor模型迅矛。也稱為異步非阻塞IO秽褒。

javaNIO:newIO->自認(rèn)為是非阻塞IO和多路復(fù)用IO的結(jié)合體

IO與NIO

傳統(tǒng)IO

  • InputStream、OutputStream 基于字節(jié)操作的 IO
  • Writer牡属、Reader 基于字符操作的 IO
  • File 基于磁盤操作的 IO
  • Socket 基于網(wǎng)絡(luò)操作的 IO

javaIO:面向流逮栅、 阻塞IO
javaNIO :面向緩存措伐、非阻塞IO、選擇器

NIO 方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu)

java的IO模型-IO發(fā)展歷程 BIO/NIO/AIO

BIO

同步阻塞I/O模式粪躬,數(shù)據(jù)的讀取寫入必須阻塞在一個線程內(nèi)等待其完成镰官。


image.png

采用 BIO 通信模型 的服務(wù)端狈网,通常由一個獨立的 Acceptor 線程負(fù)責(zé)監(jiān)聽客戶端的連接拓哺。我們一般通過在while(true) 循環(huán)中服務(wù)端會調(diào)用 accept() 方法等待接收客戶端的連接的方式監(jiān)聽請求拓售,請求一旦接收到一個連接請求镶奉,就可以建立通信套接字在這個通信套接字上進(jìn)行讀寫操作哨苛,此時不能再接收其他客戶端連接請求玻侥,只能等待同當(dāng)前連接的客戶端的操作執(zhí)行完成凑兰, 不過可以通過多線程來支持多個客戶端的連接,如上圖所示波岛。
如果要讓 BIO 通信模型 能夠同時處理多個客戶端請求,就必須使用多線程(主要原因是socket.accept()曹鸠、socket.read()坛善、socket.write() 涉及的三個主要函數(shù)都是同步阻塞的)眠屎,也就是說它在接收到客戶端連接請求之后為每個客戶端創(chuàng)建一個新的線程進(jìn)行鏈路處理,處理完成之后抖拴,通過輸出流返回應(yīng)答給客戶端,線程銷毀洒放。這就是典型的 一請求一應(yīng)答通信模型 往湿。
在 Java 虛擬機(jī)中领追,線程是寶貴的資源绒窑,線程的創(chuàng)建和銷毀成本很高些膨,除此之外订雾,線程的切換成本也是很高的误甚。尤其在 Linux 這樣的操作系統(tǒng)中窑邦,線程本質(zhì)上就是一個進(jìn)程,創(chuàng)建和銷毀線程都是重量級的系統(tǒng)函數(shù)李请。如果并發(fā)訪問量增加會導(dǎo)致線程數(shù)急劇膨脹可能會導(dǎo)致線程堆棧溢出较幌、創(chuàng)建新線程失敗等問題乍炉,最終導(dǎo)致進(jìn)程宕機(jī)或者僵死,不能對外提供服務(wù)巢株。

可以用線程池替換創(chuàng)建線程困檩,減少線程上下文切換開銷窗看;雖有效提高,單并發(fā)量大的情況治標(biāo)不治本

NIO

java1.4引入倦炒,為同步非阻塞的IO框架
三大組件:buffer显沈、channel、selelctor


image.png

AIO

JDK1.7升級了NIO類庫,升級后的NIO類庫被稱為NIO2.0拉讯。也就是我們要介紹的AIO涤浇。NIO2.0引入了新的異步通道的概念,并提供了異步文件通道和異步套接字通道的實現(xiàn)魔慷。異步通道提供兩種方式獲取操作結(jié)果只锭。
(1)通過Java.util.concurrent.Future類來表示異步操作的結(jié)果;
(2)在執(zhí)行異步操作的時候傳入一個Java.nio.channels.
CompletionHandler接口的實現(xiàn)類作為操作完成的回調(diào)邀摆。
NIO2.0的異步套接字通道是真正的異步非阻塞IO例获,它對應(yīng)UNIX網(wǎng)絡(luò)編程中的事件驅(qū)動IO(AIO)收壕,它不需要通過多路復(fù)用器(Selector)對注冊的通道進(jìn)行輪詢操作即可實現(xiàn)異步讀寫端壳,從而簡化了NIO的編程模型照捡。
我們可以得出結(jié)論:異步Socket Channel是被動執(zhí)行對象鹿寨,我們不需要想NIO編程那樣創(chuàng)建一個獨立的IO線程來處理讀寫操作。對于AsynchronousServerSocketChannel和AsynchronousSocketChannel,它們都由JDK底層的線程池負(fù)責(zé)回調(diào)并驅(qū)動讀寫操作。正因為如此柔吼,基于NIO2.0新的異步非阻塞Channel進(jìn)行編程比NIO編程更為簡單培漏。

image.png

NIO主要內(nèi)容核心組件

buffer

在java NIO 中負(fù)者數(shù)據(jù)的存儲。緩沖區(qū)就是數(shù)組。用于存儲不同類型的數(shù)據(jù)。并提供了對數(shù)據(jù)結(jié)構(gòu)化訪問以及維護(hù)讀寫位置等信息。

常用子類

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

buffer核心屬性

  • capacity 緩沖區(qū)數(shù)組的總長度
  • position 下一個要操作的數(shù)據(jù)元素的位置
  • limit 緩沖區(qū)數(shù)組中不可操作的下一個元素的位置柠横,limit<=capacity
  • mark 用于記錄當(dāng)前 position 的前一個位置或者默認(rèn)是 0
0<=mark<=position<=limit<=capacity
image.png

buffer其他特性

  • 基本API
  • 快照buffer->byteBuffer.slice()
  • 只讀Buffer->byteBuffer.asReadOnlyBuffer()
  • DirectByteBuffer
  • MappedByteBuffer
  • 零拷貝

channel

我們對數(shù)據(jù)的讀取和寫入要通過Channel,它就像水管一樣屿聋,是一個通道。通道不同于流的地方就是通道是雙向的菇曲,可以用于讀孵户、寫和同時讀寫操作何址。底層的操作系統(tǒng)的通道一般都是全雙工的胁镐,所以全雙工的Channel比流能更好的映射底層操作系統(tǒng)的API。

主要實現(xiàn)類

FileChannel:用于讀取盯漂、寫入阿弃、映射和操作文件的通道。[不能設(shè)置非阻塞]
SocketChannel:通過 TCP 讀寫網(wǎng)絡(luò)中的數(shù)據(jù)旁赊。
ServerSocketChannel:可以監(jiān)聽新進(jìn)來的 TCP 連接,對每一個新進(jìn)來的連接都會創(chuàng)建一個 SocketChannel斜友。
DatagramChannel:通過 UDP 讀寫網(wǎng)絡(luò)中的數(shù)據(jù)通道。

其他特性

  • 基本API:對文件垃它、對網(wǎng)絡(luò)
  • 通道之間的數(shù)據(jù)傳輸
    • transferFrom():transferFrom()方法可以將數(shù)據(jù)從源通道傳輸?shù)紽ileChannel中
    • transferTo():transferTo()方法將數(shù)據(jù)從FileChannel傳輸?shù)狡渌腸hannel中
  • 分散(Scatter)與聚集(Gather)

selector

Selector是Java NIO 編程的核心鲜屏,也是IO多路復(fù)用體現(xiàn),Selector會不斷輪詢注冊在其上的Channel国拇,如果某個Channel上面發(fā)生讀或者寫事件洛史,這個Channel就處于就緒狀態(tài),會被Selector輪詢出來酱吝,然后通過SelectionKey可以獲取就緒Channel的集合也殖,進(jìn)行后續(xù)的I/O操作。


image.png

linux底層用的內(nèi)核函數(shù)epoll

SelectionKey

當(dāng)調(diào)用 register(Selector sel, int ops) 將通道注冊選擇器時务热,選擇器對通道的監(jiān)聽事件忆嗜,需要通過第二個參數(shù) ops 指定。
可以監(jiān)聽的事件類型(用 可使用 SelectionKey 的四個常量 表示):

  • 讀 : SelectionKey.OP_READ (1)
  • 寫 : SelectionKey.OP_WRITE (4)
  • 連接 : SelectionKey.OP_CONNECT (8)
  • 接收 : SelectionKey.OP_ACCEPT (16)

如果你對不止一種事件感興趣崎岂,那么可以用“位或”操作符將常量連接起來
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

javaNIO編寫簡要步驟【以服務(wù)端為例】


// 1.打開ServerSocketChannel捆毫,用戶監(jiān)聽連接的管道
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
// 2.綁定端口,并設(shè)置為非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8899));
// 3.創(chuàng)建多路復(fù)用器Selector
Selector selector=Selector.open();
// 4.將ServerSocketChannel管道注冊到多路復(fù)用器Selector上冲甘,并指定所關(guān)心的事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 5.Selector無限循環(huán)準(zhǔn)備就緒的key
selector.select();
Set<SelectionKey> selectionKeys=selector.selectedKeys();    
// 6.Selector監(jiān)聽到有新的客戶端接入绩卤,處理新的接入請求,完成TCP三次握手,并設(shè)置非阻塞
// 將新接入的客戶端連接注冊Selector江醇,監(jiān)聽讀操作濒憋,用來讀取客戶端發(fā)送的網(wǎng)絡(luò)消息
ServerSocketChannel serverSocketChannel1= (ServerSocketChannel) selectionKey.channel();
client=serverSocketChannel1.accept();
client.configureBlocking(false);
client.register(selector,SelectionKey.OP_READ);
// 7.讀取客戶端的消息到buffer中
client= (SocketChannel) selectionKey.channel();
ByteBuffer readBuffer=ByteBuffer.allocate(1024);
int count=client.read(readBuffer);
// 8.最終寫入客戶端
socketChannel.write(writeBuffer);


epoll簡單說下

epoll是一種I/O事件通知機(jī)制,是linux 內(nèi)核實現(xiàn)IO多路復(fù)用的一個實現(xiàn)陶夜。
IO多路復(fù)用是指凛驮,在一個操作里同時監(jiān)聽多個輸入輸出源,在其中一個或多個輸入輸出源可用的時候返回律适,然后對其的進(jìn)行讀寫操作辐烂。

數(shù)據(jù)結(jié)構(gòu):紅黑樹加鏈表
圍繞3個API展開:
1.epoll_create:創(chuàng)建實例
2.epoll_ctl:對紅黑樹進(jìn)行管理
3.epoll_wait:阻塞等待注冊的事件發(fā)生


相關(guān)測試代碼
BioTest.java
ChannelScatteringAndGatherIng.java
channelTransfer.java
NioBuffer1.java
NioBuffer2.java
NioBuffer3.java
NioBufferChannel4.java
NioBufferChannel5.java
NioByfferChannel6.java
NIOClient.java
NIOServer.java

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市捂贿,隨后出現(xiàn)的幾起案子纠修,更是在濱河造成了極大的恐慌,老刑警劉巖厂僧,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扣草,死亡現(xiàn)場離奇詭異,居然都是意外死亡颜屠,警方通過查閱死者的電腦和手機(jī)辰妙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甫窟,“玉大人密浑,你說我怎么就攤上這事〈志” “怎么了尔破?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長浇衬。 經(jīng)常有香客問我懒构,道長,這世上最難降的妖魔是什么耘擂? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任胆剧,我火速辦了婚禮,結(jié)果婚禮上醉冤,老公的妹妹穿的比我還像新娘秩霍。我一直安慰自己,他們只是感情好蚁阳,可當(dāng)我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布前域。 她就那樣靜靜地躺著,像睡著了一般韵吨。 火紅的嫁衣襯著肌膚如雪匿垄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天归粉,我揣著相機(jī)與錄音椿疗,去河邊找鬼。 笑死糠悼,一個胖子當(dāng)著我的面吹牛届榄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倔喂,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼铝条,長吁一口氣:“原來是場噩夢啊……” “哼靖苇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起班缰,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤贤壁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后埠忘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脾拆,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年莹妒,在試婚紗的時候發(fā)現(xiàn)自己被綠了名船。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡旨怠,死狀恐怖渠驼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鉴腻,我是刑警寧澤渴邦,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站拘哨,受9級特大地震影響谋梭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜倦青,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一瓮床、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧产镐,春花似錦隘庄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至述雾,卻和暖如春街州,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玻孟。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工唆缴, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人黍翎。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓面徽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子趟紊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容