javanio,Javanio示例

本文目錄一覽:

什麼是Java NIO,它的工作原理是什麼

Java NIO是在jdk1.4開始使用的,它既可以說成「新I/O」,也可以說成非阻塞式I/O。

1. 由一個專門的線程來處理所有的 IO 事件,並負責分發。

2. 事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。

3. 線程通訊:線程之間通過 wait,notify 等方式通訊。保證每次上下文切換都是有意義的。減少無謂的線程切換。

Java NIO怎麼理解通道和非阻塞

nio引入了buffer、channel、selector等概念。

通道相當於之前的I/O流。

「通道」太抽象了。java解釋不清的東西只能看它底層是怎麼解釋的——操作系統的I/O控制,通道控制方式?

I/O設備:CPU——通道——設備控制器——I/O設備

(通道和設備控制器的關係是多對多,設備控制器和I/O設備的關係也是多對多。)

I/O過程,參考;DMA.htm:

1.CPU在執行用戶程序時遇到I/O請求,根據用戶的I/O請求生成通道程序(也可以是事先編好的)。放到內存中,並把該通道程序首地址放入CAW中。

2.CPU執行「啟動I/O」指令,啟動通道工作。

3.通道接收「啟動I/O」指令信號,從CAW(記錄下一條通道指令存放的地址)中取出通道程序首地址,並根據此地址取出通道程序的第一條指令,放入CCW(記錄正在執行的通道指令)中;同時向CPU發回答信號,通知「啟動I/O」指令完成完畢,CPU可繼續執行。

4.與此同時,通道開始執行通道程序,進行物理I/O操作。當執行完一條指令後,如果還有下一條指令則繼續執行;否則表示傳輸完成,同時自行停止,通知CPU轉去處理通道結束事件,並從CCW中得到有關通道狀態。

如此一來,主處理器只要發出一個I/O操作命令,剩下的工作完全由通道負責。I/O操作結束後,I/O通道會發出一個中斷請求,表示相應操作已完成。

通道控制方式是對數據塊進行處理的,並非位元組。

通道控制方式就是非同步I/O,參考:

I/O分兩段:1.數據從I/O設備到內核緩衝區。2.數據從內核緩衝區到應用緩衝區

I/O類型:

1.非同步I/O不會產生阻塞,程序不會等待I/O完成,繼續執行代碼,等I/O完成了再執行一個什麼回調函數,代碼執行效率高。很容易聯想到ajax。這個一般用於I/O操作不影響之後的代碼執行。

2.阻塞I/O,程序發起I/O操作後,進程阻塞,CPU轉而執行其他進程,I/O的兩個步驟完成後,向CPU發送中斷信號,進程就緒,等待執行。

3.非阻塞I/O並非都不阻塞,其實是第一步不阻塞,第二部阻塞。程序發起I/O操作後,進程一直檢查第一步是否完成,CPU一直在循環詢問,完成後,進程阻塞直到完成第二步。明白了!這個是「站著茅坑不拉屎」,CPU利用率最低的。邏輯和操作系統的程序直接控制方式一樣。

阻塞不阻塞描述的是發生I/O時當前線程的狀態。

以上是操作系統的I/O,那麼java的nio又是怎樣的呢?

個人覺得是模仿了通道控制方式。

先看看nio的示例代碼:

服務端TestReadServer.java

import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class TestReadServer { /*標識數字*/ private int flag = 0; /*緩衝區大小*/ private int BLOCK = 1024*1024*10; /*接受數據緩衝區*/ private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK); /*發送數據緩衝區*/ private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); private Selector selector; public TestReadServer(int port) throws IOException { // 打開伺服器套接字通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 伺服器配置為非阻塞 serverSocketChannel.configureBlocking(false); // 檢索與此通道關聯的伺服器套接字 ServerSocket serverSocket = serverSocketChannel.socket(); // 進行服務的綁定 serverSocket.bind(new InetSocketAddress(port)); // 通過open()方法找到Selector selector = Selector.open(); // 註冊到selector,等待連接 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println(“Server Start—-“+port+”:”); } // 監聽 private void listen() throws IOException { while (true) { // 選擇一組鍵,並且相應的通道已經打開 selector.select(); // 返回此選擇器的已選擇鍵集。 SetSelectionKey selectionKeys = selector.selectedKeys(); IteratorSelectionKey iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); handleKey(selectionKey); } } } // 處理請求 private void handleKey(SelectionKey selectionKey) throws IOException { // 接受請求 ServerSocketChannel server = null; SocketChannel client = null; String receiveText; String sendText; int count=0; // 測試此鍵的通道是否已準備好接受新的套接字連接。 if (selectionKey.isAcceptable()) { // 返回為之創建此鍵的通道。 server = (ServerSocketChannel) selectionKey.channel(); // 接受到此通道套接字的連接。 // 此方法返回的套接字通道(如果有)將處於阻塞模式。 client = server.accept(); // 配置為非阻塞 client.configureBlocking(false); // 註冊到selector,等待連接 client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { // 返回為之創建此鍵的通道。 client = (SocketChannel) selectionKey.channel(); //將緩衝區清空以備下次讀取 receivebuffer.clear(); //讀取伺服器發送來的數據到緩衝區中 System.out.println(System.currentTimeMillis()); count = client.read(receivebuffer); System.out.println(System.currentTimeMillis() + “~”+count); } } /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub int port = 1234; TestReadServer server = new TestReadServer(port); server.listen(); } }客戶端TestReadClient.javaimport java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class TestReadClient { /*標識數字*/ private static int flag = 0; /*緩衝區大小*/ private static int BLOCK = 1024*1024*10; /*接受數據緩衝區*/ private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK); /*發送數據緩衝區*/ private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); /*伺服器端地址*/ private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress( “localhost”, 1234); public static void main(String[] args) throws IOException { // TODO Auto-generated method stub // 打開socket通道 SocketChannel socketChannel = SocketChannel.open(); // 設置為非阻塞方式 socketChannel.configureBlocking(false); // 打開選擇器 Selector selector = Selector.open(); // 註冊連接服務端socket動作 socketChannel.register(selector, SelectionKey.OP_CONNECT); // 連接 socketChannel.connect(SERVER_ADDRESS); // 分配緩衝區大小內存 SetSelectionKey selectionKeys; IteratorSelectionKey iterator; SelectionKey selectionKey; SocketChannel client; String receiveText; String sendText; int count=0; while (true) { //選擇一組鍵,其相應的通道已為 I/O 操作準備就緒。 //此方法執行處於阻塞模式的選擇操作。 selector.select(); //返回此選擇器的已選擇鍵集。 selectionKeys = selector.selectedKeys(); //System.out.println(selectionKeys.size()); iterator = selectionKeys.iterator(); while (iterator.hasNext()) { selectionKey = iterator.next(); if (selectionKey.isConnectable()) { System.out.println(“client connect”); client = (SocketChannel) selectionKey.channel(); // 判斷此通道上是否正在進行連接操作。 // 完成套接字通道的連接過程。 if (client.isConnectionPending()) { client.finishConnect(); System.out.println(“完成連接!”); sendbuffer.clear(); BufferedInputStream br = new BufferedInputStream(new FileInputStream(new File(“D:\BigData.zip”))); byte[] b = new byte[BLOCK]; br.read(b); sendbuffer.put(b); sendbuffer.flip(); System.out.println(System.currentTimeMillis()); client.write(sendbuffer); System.out.println(System.currentTimeMillis()); } client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { client = (SocketChannel) selectionKey.channel(); //將緩衝區清空以備下次讀取 receivebuffer.clear(); //讀取伺服器發送來的數據到緩衝區中 count=client.read(receivebuffer); if(count0){ receiveText = new String( receivebuffer.array(),0,count); System.out.println(“客戶端接受伺服器端數據–:”+receiveText); client.register(selector, SelectionKey.OP_WRITE); } } } selectionKeys.clear(); } } }例子是TestReadClient向TestReadServer發送一個本地文件。TestReadServer收到後每次列印讀取到的位元組數。

如何體現非同步I/O?

看看TestReadClient中的:

if (selectionKey.isConnectable()) { System.out.println(“client connect”); client = (SocketChannel) selectionKey.channel(); // 判斷此通道上是否正在進行連接操作。 // 完成套接字通道的連接過程。 if (client.isConnectionPending()) { client.finishConnect();如果沒有client.finishConnect();這句等待完成socket連接,可能會報異常:java.nio.channels.NotYetConnectedException

非同步的才不會管你有沒有連接成功,都會執行下面的代碼。這裡需要人為的干預。

如果要證明是java的nio單獨使用非阻塞I/O,真沒辦法!!!阻塞非阻塞要查看進程。。。

不過還有種說法,叫非同步非阻塞。上面那段,是用非同步方式創建連接,進程當然沒有被阻塞。使用了finishConnect()這是人為將程序中止,等待連接創建完成(是模仿阻塞將當前進程阻塞掉,還是模仿非阻塞不斷輪詢訪問,不重要了反正是程序卡住沒往下執行)。

所以,創建連接的過程用非同步非阻塞I/O可以解釋的通。那read/write的過程呢?

根據上面例子的列印結果,可以知道這個過程是同步的,沒執行完是不會執行下面的代碼的。至於底下是使用阻塞I/O還是非阻塞I/O,對於應用級程序來說不重要了。

阻塞還是非阻塞,對於正常的開發(創立連接,從連接中讀寫數據)並沒有多少的提升,操作過程都類似。

那NIO憑什麼成為高性能架構的基礎,比起IO,性能優越在哪裡,接著猜。。。

java nio有意模仿操作系統的通道控制方式,那他的底層是不是就是直接使用操作系統的通道?

通道中的數據是以塊為單位的,之前的流是以位元組為單位的,同樣的數據流操作外設的次數較多。代碼中channel都是針對ByteBuffer對象進行read/write的,而ByteBuffer又是ByteBuffer.allocate(BLOCK);這樣創建的,是一個連續的塊空間。

那ByteBuffer是不是也是模擬操作系統的緩存?

緩存在io也有,如BufferedInputStream。CPU和外設的速度差很多,緩存為了提高CPU使用率,等外設將數據讀入緩存後,CPU再統一操作,不用外設讀一次,CPU操作一次,CPU的效率會被拉下來。。。

java裡面的NIO是什麼,有什麼用?

NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但實現方式不同,NIO主要用到的是塊,所以NIO的效率要比IO高很多。

在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另一套就是網路編程NIO。

Java中IO與NIO的區別和使用場景

在java2以前,傳統的socket IO中,需要為每個連接創建一個線程,當並發的連接數量非常巨大時,線程所佔用的棧內存和CPU線程切換的開銷將非常巨大。java5以後使用NIO,不再需要為每個線程創建單獨的線程,可以用一個含有限數量線程的線程池,甚至一個線程來為任意數量的連接服務。由於線程數量小於連接數量,所以每個線程進行IO操作時就不能阻塞,如果阻塞的話,有些連接就得不到處理,NIO提供了這種非阻塞的能力。

NIO 設計背後的基石:反應器模式,用於事件多路分離和分派的體系結構模式。

反應器(Reactor):用於事件多路分離和分派的體系結構模式

通常的,對一個文件描述符指定的文件或設備, 有兩種工作方式: 阻塞 與非阻塞 。所謂阻塞方式的意思是指, 當試圖對該文件描述符進行讀寫時, 如果當時沒有東西可讀,或者暫時不可寫, 程序就進入等待 狀態, 直到有東西可讀或者可寫為止。而對於非阻塞狀態, 如果沒有東西可讀, 或者不可寫, 讀寫函數馬上返回, 而不會等待 。

一種常用做法是:每建立一個Socket連接時,同時創建一個新線程對該Socket進行單獨通信(採用阻塞的方式通信)。這種方式具有很高的響應速度,並且控制起來也很簡單,在連接數較少的時候非常有效,但是如果對每一個連接都產生一個線程的無疑是對系統資源的一種浪費,如果連接數較多將會出現資源不足的情況。

另一種較高效的做法是:伺服器端保存一個Socket連接列表,然後對這個列表進行輪詢,如果發現某個Socket埠上有數據可讀時(讀就緒),則調用該socket連接的相應讀操作;如果發現某個 Socket埠上有數據可寫時(寫就緒),則調用該socket連接的相應寫操作;如果某個埠的Socket連接已經中斷,則調用相應的析構方法關閉該埠。這樣能充分利用伺服器資源,效率得到了很大提高。

傳統的阻塞式IO,每個連接必須要開一個線程來處理,並且沒處理完線程不能退出。

非阻塞式IO,由於基於反應器模式,用於事件多路分離和分派的體系結構模式,所以可以利用線程池來處理。事件來了就處理,處理完了就把線程歸還。而傳統阻塞方式不能使用線程池來處理,假設當前有10000個連接,非阻塞方式可能用1000個線程的線程池就搞定了,而傳統阻塞方式就需要開10000個來處理。如果連接數較多將會出現資源不足的情況。非阻塞的核心優勢就在這裡。

為什麼會這樣,下面就對他們做進一步細緻具體的分析:

首先,我們來分析傳統阻塞式IO的瓶頸在哪裡。在連接數不多的情況下,傳統IO編寫容易方便使用。但是隨著連接數的增多,問題傳統IO就不行了。因為前面說過,傳統IO處理每個連接都要消耗一個線程,而程序的效率當線程數不多時是隨著線程數的增加而增加,但是到一定的數量之後,是隨著線程數的增加而減少。這裡我們得出結論,傳統阻塞式IO的瓶頸在於不能處理過多的連接。

然後,非阻塞式IO的出現的目的就是為了解決這個瓶頸。而非阻塞式IO是怎麼實現的呢?非阻塞IO處理連接的線程數和連接數沒有聯繫,也就是說處理 10000個連接非阻塞IO不需要10000個線程,你可以用1000個也可以用2000個線程來處理。因為非阻塞IO處理連接是非同步的。當某個鏈接發送請求到伺服器,伺服器把這個連接請求當作一個請求”事件”,並把這個”事件”分配給相應的函數處理。我們可以把這個處理函數放到線程中去執行,執行完就把線程歸還。這樣一個線程就可以非同步的處理多個事件。而阻塞式IO的線程的大部分時間都浪費在等待請求上了。

所謂阻塞式IO流,就是指在從數據流當中讀寫數據的的時候,阻塞當前線程,直到IO流可以

重新使用為止,你也可以使用流的avaliableBytes()函數看看當前流當中有多少位元組可以讀取,這樣

就不會再阻塞了。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/236137.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-12 11:58
下一篇 2024-12-12 11:58

相關推薦

發表回復

登錄後才能評論