Java NIO學習總結一(非阻塞特性)
阿新 • • 發佈:2018-12-25
NIO(New IO)是從Java 1.4版開始引入的新的IO API,其與標準JAVA IO API的差異本質上體現在資源的利用方式上,這一點可以從現實中餐廳排隊的例子來理解。午飯時間到了,小明準備從三家備選餐廳A、B、C中選擇一家就餐,糟糕的是三家餐廳的位置都滿了,小明只能選擇一家餐廳開始排隊等待,還有一個辦法就是小明讓自己的朋友小華和小麗去另外兩家排隊,這樣才能儘快就餐,這就好比傳統的的IO,沒有輸入時執行緒就一直阻塞,且每個輸入通道都需要一個執行緒來讀取。而現在流行的就餐方式是小明在A、B、C各取一個號且關注餐廳排隊就餐的微信公眾號,這時小明就可以去幹別的事情,直到微信提醒某家餐廳有位置了,好處顯而易見,既解放了生產力,又節約了時間,資源利用更高效,這就是NIO處理輸入輸出的方式。
總的來說,NIO有三大特點,第一個特點就是同時擁有阻塞模式和非阻塞模式,可以實現非同步IO,下面分別給出IO伺服器和NIO伺服器的一個例子:
解釋下以上程式碼中的一些概念: ServerSocket:Socket連線的伺服器類,可以監聽給定埠的Socket連線; accept函式:在沒有新的Socket連線時會阻塞,一旦有新連線即返回。public class IOServer { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(8189); while (true) { Socket socket = ss.accept(); Runnable runnable = new SocketHandlerThread(socket); Thread t = new Thread(runnable); t.start(); } } catch (IOException e) { e.printStackTrace(); } } } class SocketHandlerThread implements Runnable { private Socket socket; public SocketHandlerThread(Socket socket) { this.socket = socket; } @Override public void run() { try { InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); Scanner scanner = new Scanner(inputStream); PrintWriter out = new PrintWriter(outputStream, true); out.println("Enter something(enter exit to quit):"); boolean done = false; while (!done && scanner.hasNextLine()) { String line = scanner.nextLine(); out.println("Your input: " + line); if (line.trim().equalsIgnoreCase("exit")) { done = true; } } socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
本地啟動IO伺服器後,windows下調出dos控制檯,輸入telnet localhost 8189,連線上伺服器,可以輸入任意字元,輸入”exit“跟伺服器斷開連線:
可以使用多個視窗開啟多個socket連線,從程式碼中可以看出,每有一個socket連線就開啟一個處理執行緒,因為主執行緒會在accept()函式處阻塞直到有新的連線進來,所有必須新開處理執行緒來處理socket連線的輸入輸出,在socket處理執行緒中,程式會阻塞在scanner.hasNextLine()這裡,直至新的輸入進來,這就是傳統IO的阻塞特性。
public class NIOServer {
public static void main(String[] args) {
try {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8189));
ssc.configureBlocking(false); // 設定為非阻塞
List scList = new ArrayList<>();
ByteBuffer buffer = ByteBuffer.allocate(48);
while (true) {
SocketChannel sc = ssc.accept(); // 此處會阻塞
if (sc != null) {
scList.add(sc);
System.out.println("new socket");
sc.configureBlocking(false); // 設定為非阻塞
}
for (SocketChannel scTemp : scList) {
scTemp.read(buffer); // 此處會阻塞
}
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
解釋下以上程式碼中的一些概念:ServerSocketChannel:相當於IO中的ServerSocket,是NIO中Channel概念中的一種,還有FileChannel(檔案渠道)、DataGramChannel(UDP連線渠道)和SocketChannel(Socket連線渠道)。 configureBlocking函式:預設情況下NIO也是阻塞模式,通過該函式切換到非阻塞模式。 ByteBuffer:位元組緩衝區,緩衝區的一種,簡單理解為輸入輸出臨時儲存的一個地方,可參見下一篇。 flip函式:緩衝區由寫模式切換到讀模式, 關於該函式的詳細情況參見下一篇。 對於NIO伺服器,只有一個主執行緒就可以實現把所有socket連線的輸入列印到控制檯,因為程式碼中把兩處會阻塞的地方都設定為非阻塞模式,然後主執行緒掃描各個連線 的輸入,再將其輸出到控制檯,由於非阻塞特性,主執行緒就可以在監聽連線的同時還可以監聽已有連線的輸入,對於多連線少輸入的系統,這種處理方式是非常經濟的。