前言
前段時間公司負責招聘的小姐姐們反應,根據應聘者答題試捲來看這一批應聘者在對IO/NIO理解不是很nice,因此本章主要介紹一下Java IO/NIO相關模型、IO包(NIO包)等內容;具體介紹思路從以下3點來闡述:
IO模型;Java IO;Java NIO;大總結。
IO模型
阻塞IO模型(blocking IO)
最傳統的一種IO模型,即在讀寫數據過程中會發生阻塞現象。當用戶線程發出IO請求之後,內核會去查看數據是否就緒,如果沒有就緒就會等待數據就緒,而用戶線程就會處於阻塞狀態,用戶線程交出CPU。當數據就緒之後,內核會將數據拷貝到用戶線程,並返回結果給用戶線程,用戶線程才解除 block 狀態。典型的阻塞IO模型的例子為: data = socket.read();如果數據沒有就緒,就會一直阻塞在 read()方法。
非阻塞IO模型(non-blocking IO)
當用戶線程發起一個 read 操作後,並不需要等待,而是馬上就得到來一個結果。如果結果是一個error時,它就知道數據還沒有準備好,於是它可以再次發送read操作。一旦內核中的數據準備好了,並且又再次收到了用戶線程的請求,那麼它馬上就將數據拷貝到了用戶線程,然後返回。所以事實上,在非阻塞IO模型中,用戶線程需要不斷的詢問內核數據是否就緒,也就是說非阻塞IO不會交出CPU,而會一直佔有CPU。典型的非阻塞IO模型一般如下:while(true){data = socket.read();if(data != error){// 處理數據 break;}}// 非阻塞IO又一個非常嚴重的問題,在while 循環中需要不斷的去詢問內核數據是否就緒,這樣會導致CPU佔用率非常高,因此一般情況下很少使用while循環這種方式來讀取數據。
多路復用IO模型(IO Multiplex)
多路復用IO模型是目前使用的比較多的模型。Java NIO實際上就是多路復用IO,在多路復用IO模型中會有一個線程不斷去輪詢多個socket的狀態,只有當socket真正有讀寫事件時,才真正調用實際的IO讀寫操作。因為在多路復用IO模型中,只需要使用一個線程就可以管理多個socket,系統不需要建立新的進程或者線程,也不必維護這些線程和進程,並且只有在真正有socket讀寫事件進行時,才會使用IO資源,所有它大大減少資源佔用。在Java NIO中,是通過 selector.select()去查詢每個通道是否有達到事件,如果沒有事件,則一直阻塞在那裡,因此這種方式會導致用戶線程的阻塞。多路復用IO模式,通過一個線程就可以管理多個socket,只有當socket真正有讀寫事件發生才會佔用資源來進行實際的讀寫操作。因此,多路復用IO比較適合連接數比較多的情況另外多路復用IO為何比非阻塞IO模型的效率高?是因為在非阻塞IO中,不斷的詢問socket狀態時通過用戶線程去進行的,而在多路復用IO中,輪詢每個socket狀態是內核在進行的,這個效率要比用戶線程要高的多。不過要注意的是,多路復用IO模型是通過輪詢的方式來檢測是否有事件到達,並且對到達的事件逐一進行響應。因此對於多路復用IO模型來說,一旦事件響應體很大,那麼就會導致後續的事件遲遲得不到處理,並且會影響新的事件輪詢。
信號驅動IO模型(signal driven I/O)
在信號驅動IO模型中,當用戶線程發起一個IO請求操作,會給對應的socket註冊一個信號函數,然後用戶線程會繼續執行,當內核數據就緒時會發送一個信號給用戶線程,用戶線程接收到信號之後,便在信號函數中調用IO讀寫操作來進行實際的IO請求操作。
異步IO模型(asynchronous I/O)
異步IO模型才是最理想的IO模型,在異步IO模型中,當用戶線程發起read操作之後,立刻就可以開始去做其它的事情。而另一方面,從內核的角度,當它受到一個asynchronout read之後,它會立刻返回,說明read請求已經成功發起來,因此不會對用戶線程產生任何block。然後,內核會等待數據準備完成,然後將數據拷貝到用戶線程,當這一切都完成之後,內核會給用戶線程發送一個信號,告訴它read操作完成了。也就是說用戶線程完全不需要實際的整個IO操作是如何進行的,只需要發起一個請求,當接收內核返回的成功信號時表示IO操作已完成,可以直接去使用數據了。也就是說在異步IO模型中,IO操作的兩個階段都不會阻塞用戶線程,這兩個階段都是由內核自動完成,然後發送一個信號告知用戶線程操作已完成。用戶線程中不需要再次調用IO函數進行具體的讀寫。這點是和信號驅動模型有所不同的,在信號驅動模型中,當用戶線程接收到信號表示數據已經就緒,然後需要用戶線程調用IO函數進行實際的讀寫操作;而在異步IO模型中,收到信號表示IO操作已經完成,不需要再在用戶線程中調用IO函數進行實際的讀寫操作。注意,異步IO是需要作業系統的底層支持,在Java 7中,提供了Asynchronous IO。
Java IO包
Java NIO
什麼是Java NIO?
java.nio全稱java non-blocking IO(實際上是 new io),是指JDK 1.4 及以上版本裡提供的新api(New IO),為所有的原始類型(boolean類型除外)提供緩存支持的數據容器,使用它可以提供非阻塞式的高伸縮性網絡。
NIO三大核心部分?
Channel(通道);Buffer(緩衝區);Selector選擇區。IO和NIO區別?
傳統 IO 是面向流、阻塞的,NIO 則是面向緩衝區、非阻塞的。
傳統IO基於字節流和字符流進行操作,而NIO基於Channel和Buffer進行操作,數據總是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。Selector用於監聽多個通道的事件(比如:連接打開、數據到達)。因此,單個線程可以監聽多個數據通道。
NIO的緩衝區
Java IO面向流意味這每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前後移動流中的數據。如果需要前後移動從流中讀取的數據,需要先將它緩存到一個緩衝區。NIO的緩衝導向方法不同,數據讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有你需要處理的數據。而且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裡尚未處理的數據。
NIO的非阻塞
IO的各種流是阻塞的,這意味著,當一個線程調用read()或write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再幹任何事情流。NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取。而不是保持線程阻塞,所有直至數據變得可以讀取之前,該線程可以繼續做其它的事情。給阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。線程通常將非阻塞IO的空閒時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。
ChannelChannel和IO中Stream是差不多一個等級的。只不過Stream是單向的,譬如:InputStream,OutputStream,而Channel是雙向的,既可以用來進行讀操作,又可以用來寫操作。
NIO中Channel的主要實現有:
FileChannelDatagramChannelSocketChannelServerSocketChannelBufferBuffer緩衝區,實際上是一個容器,是一個連續數組。Channel提供從文件、網絡讀取數據的渠道,但是讀取或寫入的數據都必須經由Buffer。
上圖描述來從一個客戶段想服務端發送數據,然後服務端就收數據的過程。客戶端發送數據時,必須先將數據存入Buffer中,然後將Buffer中的內容寫入通道。服務端這邊接收數據必須通過Channel將數據讀入到Buffer中,然後再從Buffer中取出數據來處理。
在NIO中,Buffer是一個頂層父類,它是一個抽象類,常用的buffer的子類有:ByteBuffer、IntBuffer、CharBuffer、CharBuffer、LongBuffer、DoubleBuffer、FloatBuffer、ShortBuffer。
SelectorSelector類是NIO的核心類,Selector能夠檢測多個註冊的通道上是否有事件發生,如果有事件發生,便獲取事件然後針對每個事件進行相應的響應處理。這樣一來,只是用一個但線程就可以管理多個通道,也就是管理多個連接。這樣使得只有在連接真正有讀寫事件發生時,才會調用函數來進行讀寫,就大大地減少來系統開銷,並且不必為每個連接都創建一個線程,不用去維護多個線程,並且避免來多線程之間的上下文切換導致的開銷。
大總結
blocking I/O,數據準備、數據copy 全阻塞,同步阻塞IO;nonblocking I/O,數據準備不阻塞,數據copy阻塞,非阻塞IO;I/O multiplexing,數據準備阻塞,數據copy阻塞,多路復用IO,優點可以支持更多IO請求;signal driven I/O (SIGIO),數據準備不阻塞,數據copy阻塞,異步阻塞IO;asynchronous I/O,數據準備、數據copy 全部阻塞。異步非阻塞IO。結尾
小夥伴們,此章主要是介紹的理論知識,還有一些代碼未附上,由於本人文字功底不好文中表達不清楚的望請各位小夥伴們見諒。