Java同步非阻塞模式NIO處理IO數(shù)據(jù)
一、概述
NIO(Non-Blocking IO)是同步非阻塞方式來(lái)處理IO數(shù)據(jù)。服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求一個(gè)線程,即客戶端發(fā)送的鏈接請(qǐng)求都會(huì)注冊(cè)到選擇器上,選擇器輪詢到連接有IO請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。
二、常用概念
- 同步(synchronous):調(diào)用方式指應(yīng)用(Application),調(diào)用方發(fā)起有一個(gè)功能調(diào)用時(shí),在沒(méi)有得到功能的結(jié)果之前,該調(diào)用不會(huì)返回。也就是說(shuō)調(diào)用方會(huì)一直等待被調(diào)用方返回功能的結(jié)果。
- 異步(asynchronous):調(diào)用方發(fā)起一個(gè)功能調(diào)用時(shí),沒(méi)有得到功能的結(jié)果立即返回,后續(xù)被調(diào)用方再通過(guò)回調(diào)等手段,把功能的結(jié)構(gòu)通知調(diào)用方。也就是調(diào)用方立即得到返回,但是返回中不包含執(zhí)行的結(jié)果。
同步和異步強(qiáng)調(diào)的是消息通信機(jī)制 (synchronous communication/ asynchronous communication)。所謂同步,就是在發(fā)出一個(gè)"調(diào)用"時(shí),在沒(méi)有得到結(jié)果之前,該“調(diào)用”就不返回。但是一旦調(diào)用返回,就得到返回值了。換句話說(shuō),就是由“調(diào)用者”主動(dòng)等待這個(gè)“調(diào)用”的結(jié)果。而異步則是相反,"調(diào)用"在發(fā)出之后,這個(gè)調(diào)用就直接返回了,所以沒(méi)有返回結(jié)果。換句話說(shuō),當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,調(diào)用者不會(huì)立刻得到結(jié)果。而是在"調(diào)用"發(fā)出后,"被調(diào)用者"通過(guò)狀態(tài)、通知來(lái)通知調(diào)用者,或通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用
- 阻塞:線程發(fā)起一個(gè)調(diào)用時(shí), 在調(diào)用返回之前, 線程會(huì)被阻塞, 在這個(gè)狀態(tài)下會(huì)交出當(dāng)前CPU的使用權(quán)而暫停;也就是調(diào)用方會(huì)等待調(diào)用結(jié)果, 調(diào)用阻塞了調(diào)用方的線程, 線程不在運(yùn)行處理中。
- 非阻塞:線程發(fā)起一個(gè)調(diào)用時(shí), 調(diào)用會(huì)立即返回, 避免線程被阻塞。但是, 返回的結(jié)果只是被調(diào)用方當(dāng)前狀態(tài)的值, 實(shí)際使用時(shí), 調(diào)用方需要輪詢, 直到返回結(jié)果符合預(yù)期(直到數(shù)據(jù)準(zhǔn)備好)。
阻塞和非阻塞 強(qiáng)調(diào)的是程序在等待調(diào)用結(jié)果(消息,返回值)時(shí)的狀態(tài). 阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起。調(diào)用線程只有在得到結(jié)果之后才會(huì)返回。非阻塞調(diào)用指在不能立刻得到結(jié)果之前,該調(diào)用不會(huì)阻塞當(dāng)前線程。 對(duì)于同步調(diào)用來(lái)說(shuō),很多時(shí)候當(dāng)前線程還是激活的狀態(tài),只是從邏輯上當(dāng)前函數(shù)沒(méi)有返回而已,即同步等待時(shí)什么都不干,白白占用著資源。
- 同步阻塞 IO[BIO - BlockingIO]:在此種方式下,用戶進(jìn)程在發(fā)起一個(gè) IO 操作以后,必須等待 IO 操作的完成,只有當(dāng)真正完成了 IO 操作以后,用戶進(jìn)程才能運(yùn)行。 JAVA傳統(tǒng)的 IO 模型屬于此種方式。
- 同步非阻塞 IO[Non-Blocking IO]:在此種方式下,用戶進(jìn)程發(fā)起一個(gè) IO 操作以后 邊可 返回做其它事情,但是用戶進(jìn)程需要時(shí)不時(shí)的詢問(wèn) IO 操作是否就緒,這就要求用戶進(jìn)程不停的去詢問(wèn),從而引入不必要的 CPU 資源浪費(fèi)。其中目前 JAVA 的 NIO 就屬于同步非阻塞 IO 。
- 異步阻塞 IO[IO Multiplexing]:此種方式下是指應(yīng)用發(fā)起一個(gè) IO 操作以后,不等待內(nèi)核 IO 操作的完成,等內(nèi)核完成 IO 操作以后會(huì)通知應(yīng)用程序,這其實(shí)就是同步和異步最關(guān)鍵的區(qū)別,同步必須等待或者主動(dòng)的去詢問(wèn) IO 是否完成,那么為什么說(shuō)是阻塞的呢?因?yàn)榇藭r(shí)是通過(guò) select 系統(tǒng)調(diào)用來(lái)完成的,而 select 函數(shù)本身的實(shí)現(xiàn)方式是阻塞的,而采用 select 函數(shù)有個(gè)好處就是它可以同時(shí)監(jiān)聽(tīng)多個(gè)文件句柄,從而提高系統(tǒng)的并發(fā)性!
- 異步非阻塞 IO[Asynchronous IO]: 在此種模式下,用戶進(jìn)程只需要發(fā)起一個(gè) IO 操作然后立即返回,等 IO 操作真正的完成以后,應(yīng)用程序會(huì)得到 IO 操作完成的通知,此時(shí)用戶進(jìn)程只需要對(duì)數(shù)據(jù)進(jìn)行處理就好了,不需要進(jìn)行實(shí)際的 IO 讀寫(xiě)操作,因?yàn)?真正的 IO讀取或者寫(xiě)入操作已經(jīng)由 內(nèi)核完成了。目前 Java 中還沒(méi)有支持此種 IO 模型。
三、NIO的實(shí)現(xiàn)原理
Java的NIO主要由三個(gè)核心部分組成:Channel(通道)、Buffer(緩沖區(qū))、Selector。
所有的IO在NIO中都從一個(gè)Channel開(kāi)始,數(shù)據(jù)可以從Channel讀到Buffer中,也可以從Buffer寫(xiě)到Channel中。Channel有好幾種類(lèi)型,其中比較常用的有FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel等,這些通道涵蓋了UDP和TCP網(wǎng)絡(luò)IO以及文件IO。
Buffer本質(zhì)上是一塊可以寫(xiě)入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被包裝成NIO Buffer對(duì)象,并提供了一組方法,用來(lái)方便的訪問(wèn)該塊內(nèi)存。Java NIO里關(guān)鍵的Buffer實(shí)現(xiàn)有CharBuffer、ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。這些Buffer覆蓋了你能通過(guò)IO發(fā)送的基本數(shù)據(jù)類(lèi)型,即byte、short、int、long、float、double、char。
Buffer對(duì)象包含三個(gè)重要的屬性,分別是capacity、position、limit,其中position和limit的含義取決于Buffer處在讀模式還是寫(xiě)模式。但不管Buffer處在什么模式,capacity的含義總是一樣的。
capacity:作為一個(gè)內(nèi)存塊,Buffer有個(gè)固定的最大值,就是capacity。Buffer只能寫(xiě)capacity個(gè)數(shù)據(jù),一旦Buffer滿了,需要將其清空才能繼續(xù)寫(xiě)數(shù)據(jù)往里寫(xiě)數(shù)據(jù)。
position:當(dāng)寫(xiě)數(shù)據(jù)到Buffer中時(shí),position表示當(dāng)前的位置。初始的position值為0。當(dāng)一個(gè)數(shù)據(jù)寫(xiě)到Buffer后, position會(huì)向前移動(dòng)到下一個(gè)可插入數(shù)據(jù)的Buffer單元。position最大可為capacity–1。當(dāng)讀取數(shù)據(jù)時(shí),也是從某個(gè)特定位置讀。當(dāng)將Buffer從寫(xiě)模式切換到讀模式,position會(huì)被重置為0。當(dāng)從Buffer的position處讀取數(shù)據(jù)時(shí),position向前移動(dòng)到下一個(gè)可讀的位置。
limit:在寫(xiě)模式下,Buffer的limit表示最多能往Buffer里寫(xiě)多少數(shù)據(jù),此時(shí)limit等于capacity。當(dāng)切換Buffer到讀模式時(shí), limit表示你最多能讀到多少數(shù)據(jù),此時(shí)limit會(huì)被設(shè)置成寫(xiě)模式下的position值。
Selector允許單線程處理多個(gè) Channel,如果你的應(yīng)用打開(kāi)了多個(gè)連接(通道),但每個(gè)連接的流量都很低,使用Selector就會(huì)很方便。要使用Selector,得向Selector注冊(cè)Channel,然后調(diào)用它的select()方法。這個(gè)方法會(huì)一直阻塞到某個(gè)注冊(cè)的通道有事件就緒。一旦這個(gè)方法返回,線程就可以處理這些事件,事件例如有新連接進(jìn)來(lái),數(shù)據(jù)接收等。
四、NIO代碼實(shí)現(xiàn)
客戶端實(shí)現(xiàn)
步驟
- 創(chuàng)建SocketChannel 通道
- 切換異步非阻塞模式configureBlocking(false)
- 設(shè)置緩沖區(qū)大小ByteBuffer.allocate(1024)
- 值寫(xiě)入緩沖區(qū) buffer.put(input.getBytes())
- 緩沖區(qū)中的值寫(xiě)入通道中channel.write()
代碼演示
public static void main(String[] args) throws IOException { //創(chuàng)建通道 SocketChannel channel=SocketChannel.open(new InetSocketAddress("127.0.0.1",6001)); //切換異步非阻塞模式 channel.configureBlocking(false); //設(shè)置緩沖去大小 ByteBuffer buffer=ByteBuffer.allocate(1024); System.out.println("輸入傳輸值:"); //獲取鍵盤(pán)輸入的值 Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ String input=scanner.next(); //把獲取的值寫(xiě)入緩沖區(qū)中 buffer.put(input.getBytes()); buffer.flip(); //把緩沖區(qū)中的值寫(xiě)入通道中 channel.write(buffer); buffer.clear(); } channel.close(); }
服務(wù)端實(shí)現(xiàn)
步驟
- 創(chuàng)建ServerSocketChannel通道
- 切換異步非阻塞模式configureBlocking(false)
- 綁定連接
- 獲取選擇器 Selector open = Selector.open()
- 將通道注冊(cè)到選擇器,并指定監(jiān)聽(tīng)接受事件
- 輪訓(xùn)式獲取選擇已準(zhǔn)備就緒的事件
- 獲取當(dāng)前選擇器所有注冊(cè)的監(jiān)聽(tīng)事件
- 獲取準(zhǔn)備就緒的事件
- 判斷是什么事件準(zhǔn)備就緒
- 接受就緒,獲取客戶端連接
- 設(shè)置非阻塞異步模式
- 將通道注冊(cè)到服務(wù)器上
代碼演示
public static void main(String[] args) throws IOException { //創(chuàng)建通道 ServerSocketChannel channel=ServerSocketChannel.open(); //切換到異步非阻塞模式 channel.configureBlocking(false); //綁定鏈接 channel.bind(new InetSocketAddress(6001)); //獲取選擇器 Selector open = Selector.open(); //將通道注冊(cè)到選擇器,并指定監(jiān)聽(tīng)接受事件 channel.register(open, SelectionKey.OP_ACCEPT); //輪訓(xùn)式獲取選擇已經(jīng)準(zhǔn)備就緒的事件 while(open.select() > 0) { //獲取當(dāng)前選擇器所有注冊(cè)的監(jiān)聽(tīng)事件 Iterator<SelectionKey> it = open.selectedKeys().iterator(); while(it.hasNext()) { //獲取準(zhǔn)備就緒的事件 SelectionKey sk = it.next(); //判斷是什么事件準(zhǔn)備就緒 if(sk.isAcceptable()) { //接受就緒,獲取客戶端連接 SocketChannel sc = channel.accept(); //設(shè)置非阻塞異步模式 sc.configureBlocking(false); //將通道注冊(cè)到服務(wù)器上 sc.register(open, SelectionKey.OP_READ); } else if(sk.isReadable()) { //獲取當(dāng)前選擇器就緒的通道 SocketChannel s = (SocketChannel) sk.channel(); ByteBuffer bb = ByteBuffer.allocate(1024); int len = 0; while((len = s.read(bb)) > 0) { bb.flip(); System.out.println(new String(bb.array(),0,len)); bb.clear(); } } } it.remove(); } }
五、同步非阻塞NIO總結(jié)
同步非阻塞的特點(diǎn):應(yīng)用程序的線程需要不斷的進(jìn)行IO系統(tǒng)調(diào)用,輪詢數(shù)據(jù)是否已經(jīng)準(zhǔn)備好,如果沒(méi)有準(zhǔn)備好,就繼續(xù)輪詢,直到完成IO系統(tǒng)調(diào)用為止。
同步非阻塞IO的特點(diǎn):每次發(fā)起的IO系統(tǒng)調(diào)用,在內(nèi)核等待數(shù)據(jù)過(guò)程中可以立即返回。用戶線程不會(huì)被阻塞,實(shí)時(shí)性較好。
同步非阻塞IO的缺點(diǎn): 不斷地輪詢內(nèi)核,這將占用大量的CPU時(shí)間,效率低下。
總體來(lái)說(shuō),在高并發(fā)應(yīng)用場(chǎng)景下,同步非阻塞IO也是不可用的。一般Web服務(wù)器不適用這種IO模型。這種IO模型一般很少直接使用,而是在其他IO模型中使用非阻塞IO這一特性。
以上就是Java同步非阻塞模式NIO處理IO數(shù)據(jù)的詳細(xì)內(nèi)容,更多關(guān)于Java處理IO數(shù)據(jù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入理解Java設(shè)計(jì)模式之職責(zé)鏈模式
這篇文章主要介紹了JAVA設(shè)計(jì)模式之職責(zé)鏈模式的的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解2021-11-11Java中LinkedHashSet、LinkedHashMap源碼詳解
這篇文章主要介紹了Java中LinkedHashSet、LinkedHashMap源碼詳解,LinkedHashMap是一個(gè)以雙向鏈表的方式將Entry節(jié)點(diǎn)鏈接起來(lái)的HashMap子類(lèi),它在HashMap的基礎(chǔ)上實(shí)現(xiàn)了更多的功能,具有順序存儲(chǔ)和遍歷的特性,需要的朋友可以參考下2023-09-09SpringBoot預(yù)防XSS攻擊的實(shí)現(xiàn)
XSS攻擊是一種在web應(yīng)用中的計(jì)算機(jī)安全漏洞,它允許惡意web用戶將代碼植入到提供給其它用戶使用的頁(yè)面,本文主要介紹了SpringBoot預(yù)防XSS攻擊的實(shí)現(xiàn),感興趣的可以了解一下2023-08-08Mybatis-Plus多表關(guān)聯(lián)查詢的使用案例解析
這篇文章主要介紹了Mybatis-Plus多表關(guān)聯(lián)查詢的使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05java面試突擊之sleep和wait有什么區(qū)別詳析
按理來(lái)說(shuō)sleep和wait本身就是八竿子打不著的兩個(gè)東西,但是在實(shí)際使用中大家都喜歡拿他們來(lái)做比較,或許是因?yàn)樗鼈兌伎梢宰尵€程處于阻塞狀態(tài),這篇文章主要給大家介紹了關(guān)于java面試突擊之sleep和wait有什么區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-02-02