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