更新時間:2022-05-17 09:51:42 來源:動力節(jié)點 瀏覽2567次
(1)阻塞(Block)和非租用(NonBlock):
阻塞和非阻塞是在進程訪問數(shù)據(jù)時處理數(shù)據(jù)是否就緒的一種方式。數(shù)據(jù)未就緒時阻塞:往往需要等待緩沖區(qū)中的數(shù)據(jù)就緒后再處理其他事情,否則會一直阻塞隊列。在那里等著。
非阻塞:當我們的進程訪問我們的數(shù)據(jù)緩沖區(qū)時,如果數(shù)據(jù)沒有準備好,則直接返回,無需等待。如果數(shù)據(jù)準備好了,也直接返回
(2)同步和異步方法:
同步和異步都基于應(yīng)用程序的私有操作系統(tǒng)處理 IO 事件的方式。比如同步:應(yīng)用程序直接參與IO讀寫操作。異步:所有IO讀寫都交給操作系統(tǒng)處理,應(yīng)用只需要等待通知即可。
在同步模式下處理IO事件時,必須阻塞在某個方法上,等待我們的IO事件完成(阻塞IO事件或者通過輪詢IO事件)。對于異步,所有的IO讀寫都交給操作系統(tǒng)來推。這時候,我們可以做其他事情,而不是完成真正的IO操作。當操作完成 IO 時,它會給我們的應(yīng)用程序一個通知
同步:阻塞直到 IO 事件,阻塞直到讀變?yōu)閷?。這時候我們根本不能自己動手,讓讀寫方法加到線程中,然后阻塞線程來實現(xiàn),線程的性能開銷比較大。
塊 IO 和非塊 IO
(1)區(qū)別
| IO模型 | I | NIO |
|---|---|---|
| 方式 | 從硬盤到內(nèi)存 | 從內(nèi)存到硬盤 |
| 溝通 | 面對溪流(鄉(xiāng)間小路) | 面向緩存(高速公路、多路復(fù)用技術(shù)) |
| 處理 | 阻塞 IO(多線程) | 非阻塞 IO(反應(yīng)堆) |
| 扳機 | 沒有 | 選擇器(輪詢機制) |
(2)Stream Oriented vs Buffer Oriented
Java NIO 和 IO 的第一個大區(qū)別是 IO 是面向流的。NIO 是面向緩沖區(qū)的。Java IO是面向流的,意味著每次從流中讀取一個字節(jié),直到所有字節(jié)都被讀取完畢,它們不會被緩存到任何地方,而且它不能在流中來回移動數(shù)據(jù)。如果需要來回移動從流中讀取的數(shù)據(jù),則需要先將其緩沖在緩沖區(qū)中。Java NIO 的面向緩沖區(qū)的方法略有不同。數(shù)據(jù)被讀入稍后處理的緩沖區(qū),根據(jù)需要在緩沖區(qū)中來回移動。這增加了處理的靈活性。但是,您還需要檢查緩沖區(qū)是否包含您需要處理的所有數(shù)據(jù)。另外,確保當更多數(shù)據(jù)被讀入緩沖區(qū)時,它不會覆蓋緩沖區(qū)中未處理的數(shù)據(jù)。
(3)阻塞和非阻塞Java IO的各種流都是阻塞的。
這意味著當線程調(diào)用 read() 或 write() 時,線程會被阻塞,直到讀取了一些數(shù)據(jù),或者數(shù)據(jù)被完全寫入。在此期間線程不能再做任何事情。Java NIO 的非阻塞模式使線程可以發(fā)送請求從通道讀取數(shù)據(jù),但它只能獲取當前可用的數(shù)據(jù)。如果當前沒有可用的數(shù)據(jù),它不會得到任何東西。線程可以繼續(xù)做其他事情,直到數(shù)據(jù)可供讀取,而不是保持線程阻塞。非阻塞寫入也是如此。線程請求將一些數(shù)據(jù)寫入通道,但不需要等待它完全寫入,線程可以在此期間做其他事情。線程通常會花費非阻塞 IO 空閑時間在其他通道上執(zhí)行 IO 操作,因此單個線程現(xiàn)在可以管理多個輸入和輸出通道(通道)。
(4)選擇器(Selector)
Java NIO的選擇器允許單線程監(jiān)控多個輸入通道,可以注冊多個通道使用一個選擇器,然后使用單獨的線程“選擇”通道:這些通過已經(jīng)有傳入可以被處理,或者選擇一個準備好寫入的通道。這種選擇機制使單個線程可以輕松管理多個通道。
(5)NIO和BIO讀取文件
BIO讀取文件
BIO從阻塞流中逐行讀取數(shù)據(jù)

NIO讀取文件:
通道是數(shù)據(jù)的載體,緩沖區(qū)是數(shù)據(jù)存放的地方,線程從緩沖區(qū)中檢查數(shù)據(jù)并每次通知通道

(6)處理數(shù)據(jù)的線程數(shù)
NIO:一個線程管理多個連接
BIO:一個線程管理一個連接
在 Java 1.4 之前的 I/O 系統(tǒng)中,提供的都是面向流的 I/O 系統(tǒng)。系統(tǒng)一次處理一個字節(jié)的數(shù)據(jù),一個輸入流產(chǎn)生一個字節(jié)的數(shù)據(jù),一個輸出流消耗一個字節(jié) 面向流的I/O很慢,Java 1.4引入了NIO,是面向塊的輸入輸出系統(tǒng)。系統(tǒng)以塊為單位進行處理,每個操作都是一步生成或消耗的。對于數(shù)據(jù)庫,以塊為單位處理數(shù)據(jù)比以字節(jié)為單位處理數(shù)據(jù)要快得多。
NIO中有幾個核心對象需要掌握:Buffer、Channel、Selector。

緩沖區(qū)實際上是一個容器對象,或者更直接地說,它實際上是一個數(shù)組。在 NIO 庫中,所有數(shù)據(jù)都使用緩沖區(qū)進行處理。讀取數(shù)據(jù)時,直接讀入緩沖區(qū);寫入數(shù)據(jù)時,也寫入緩沖區(qū);每次訪問 NIO 中的數(shù)據(jù)時,都會將其放入緩沖區(qū)。在面向流的 I/O 系統(tǒng)中,所有數(shù)據(jù)都直接寫入或直接讀取到 Stream 對象中。
在 NIO 中,所有的緩沖區(qū)類型都繼承自抽象類 Buffer。最常用的是ByteBuffer。對于Java中的基本類型,基本上都有特定的Buffer類型與之對應(yīng)。它們之間的繼承關(guān)系如下圖所示。
(1)四個屬性的

含義如下:
容量:緩沖區(qū)可以容納的最大數(shù)據(jù)元素數(shù)。此容量是在創(chuàng)建緩沖區(qū)時設(shè)置的,并且永遠無法更改。
上限(Limit):緩沖區(qū)的第一個不能被讀寫的元素。換句話說,緩沖區(qū)中現(xiàn)有元素的計數(shù)。
位置:要讀取或?qū)懭氲南乱粋€元素的索引。該位置由相應(yīng)的 get( ) 和 put( ) 函數(shù)自動更新。
標記:要讀取或?qū)懭氲南乱粋€元素的索引。該位置由相應(yīng)的 get( ) 和 put( ) 函數(shù)自動更新。
(2)Buffer的常用方法如下:
flip():將寫模式轉(zhuǎn)換為讀模式
rewind():將位置重置為0,一般用于重復(fù)讀。
clear() :
compact(): 將未讀數(shù)據(jù)復(fù)制到緩沖區(qū)的頭部。
mark(): reset():mark 可以標記一個位置,reset 可以重置到那個位置。
緩沖區(qū)常見類型: ByteBuffer 、 MappedByteBuffer 、 CharBuffer 、 DoubleBuffer 、 FloatBuffer 、 IntBuffer 、 LongBuffer 、 ShortBuffer 。
(3)基本操作
緩沖區(qū)基本操作:鏈接
緩沖區(qū)分片、緩沖區(qū)分配、直接緩沖區(qū)、緩沖區(qū)映射、緩沖區(qū)只讀:鏈接
(4) 緩沖區(qū)訪問數(shù)據(jù)流
存儲數(shù)據(jù)時,位置為++,停止讀取數(shù)據(jù)時,
調(diào)用flip()。此時limit=position,
讀取數(shù)據(jù)時position=0,position++,直到limit
clear()清空緩沖區(qū),準備再次寫入(position變?yōu)?,limit變?yōu)槿萘?。
通道是一個可以讀寫數(shù)據(jù)的對象,當然所有的數(shù)據(jù)都是通過一個Buffer對象來處理的。我們從不直接將字節(jié)寫入通道,而是將數(shù)據(jù)寫入包含一個或多個字節(jié)的緩沖區(qū)。此外,不是直接從通道讀取字節(jié),而是從通道將數(shù)據(jù)讀取到緩沖區(qū)中,然后從緩沖區(qū)中檢索字節(jié)。

在 NIO 中,提供了多種通道對象,所有通道對象都實現(xiàn)了 Channel 接口。它們之間的繼承關(guān)系如下圖所示:

(1)使用NIO讀取
數(shù)據(jù)前面我們說過,每當讀取數(shù)據(jù)時,并不是直接從channel讀取,而是從channel讀取到buffer。所以使用 NIO 讀取數(shù)據(jù)可以分為以下三個步驟:
1)從 FileInputStream 中獲取 Channel
2)創(chuàng)建 Buffer
3)從 Channel 讀取數(shù)據(jù)到 Buffer
示例:鏈接
(2)使用NIO寫入數(shù)據(jù)
使用NIO寫入數(shù)據(jù)的過程與讀取數(shù)據(jù)的過程類似。同樣,數(shù)據(jù)不是直接寫入通道,而是寫入緩沖區(qū),可以分為以下三個步驟:
1)從 FileInputStream中獲取通道
2)創(chuàng)建緩沖區(qū)
3)將數(shù)據(jù)從通道寫入緩沖區(qū)
示例:鏈接
(1)阻塞IO模型
在舊的IO包中,serverSocket和socket都是阻塞的,所以一旦出現(xiàn)大規(guī)模并發(fā)行為,每次訪問都會開啟一個新線程。這時候會有大規(guī)模的線程上下文切換操作(因為都在等待,所以資源都被現(xiàn)有線程吃掉了),這個時候不管是等待線程還是處理線程,響應(yīng)率會下降,并且影響新線程。

(2) NIO
Java NIO 在 jdk1.4 中使用,可以稱為“新 IO”或非阻塞 I/O。下面是java NIO的工作原理:
1)一個專門的線程處理所有的IO事件,負責(zé)分發(fā)。
2)事件驅(qū)動機制:事件到達時觸發(fā),而不是同步監(jiān)聽事件。
3)線程通信:線程之間通過wait、notify等方式進行通信,確保每一次上下文切換都是有意義的。減少不必要的線程切換。

注意:每個線程的處理流程大概是讀取數(shù)據(jù)、解碼、計算、編碼、發(fā)送響應(yīng)。
傳統(tǒng)的服務(wù)器/客戶端模型將基于 TPR(每個請求的線程)。服務(wù)器將為每個客戶端請求創(chuàng)建一個線程。該線程單獨處理客戶端請求。這個模型的一個問題是線程的數(shù)量急劇增加。大量線程會增加服務(wù)器的開銷。為了避免這個問題,大多數(shù)實現(xiàn)都采用線程池模型,并設(shè)置線程池中的最大線程數(shù),這帶來了新的問題。如果線程池1線程中有200個線程,有200個用戶在下載大文件,那么第201個用戶的請求無法及時處理,即使第201個用戶只想請求幾個KB大小的頁面。傳統(tǒng)的 Sorvor/Client 模式如下:

NIO 中的非阻塞 IO 采用基于 Reactor 模式的工作方式,IO 調(diào)用不會被阻塞,而是注冊具有有趣特性的 IO 事件,例如可讀數(shù)據(jù)的到來, new Sockets等,系統(tǒng)會在發(fā)生hold rate事件時通知我們。Selector,NlO中非阻塞IO的核心設(shè)計,是注冊各種IO事件的地方,當這些事件發(fā)生時,這個對象告訴我們發(fā)生了什么。

當任何注冊的事件發(fā)生時,如讀或?qū)?,都可以從Selector中獲取對應(yīng)的SelectionKey,從SelectionKey中可以找到該事件和該事件發(fā)生的具體SelectableChannel,從而獲取客戶端發(fā)送的數(shù)據(jù)。
在 NIO 中使用非阻塞 IO 編寫服務(wù)器處理程序需要三個步驟。
(1)將感興趣的事件注冊到Selector對象中
(2)從Selector中獲取感興趣的事件
(3)根據(jù)不同的事件進行相應(yīng)的處理
通過上述介紹,相信大家對Nio原理已經(jīng)有所了解,大家如果對此比較感興趣,想了解更多相關(guān)知識,不妨來關(guān)注一下動力節(jié)點的NIO視頻教程,視頻教程由淺到深,通俗易懂,很適合沒有基礎(chǔ)的小伙伴學(xué)習(xí),希望對大家能夠有所幫助哦。