1. 程式人生 > >Java NIO學習總結一(非阻塞特性)

Java NIO學習總結一(非阻塞特性)

NIO(New IO)是從Java 1.4版開始引入的新的IO API,其與標準JAVA IO API的差異本質上體現在資源的利用方式上,這一點可以從現實中餐廳排隊的例子來理解。午飯時間到了,小明準備從三家備選餐廳A、B、C中選擇一家就餐,糟糕的是三家餐廳的位置都滿了,小明只能選擇一家餐廳開始排隊等待,還有一個辦法就是小明讓自己的朋友小華和小麗去另外兩家排隊,這樣才能儘快就餐,這就好比傳統的的IO,沒有輸入時執行緒就一直阻塞,且每個輸入通道都需要一個執行緒來讀取。而現在流行的就餐方式是小明在A、B、C各取一個號且關注餐廳排隊就餐的微信公眾號,這時小明就可以去幹別的事情,直到微信提醒某家餐廳有位置了,好處顯而易見,既解放了生產力,又節約了時間,資源利用更高效,這就是NIO處理輸入輸出的方式。

總的來說,NIO有三大特點,第一個特點就是同時擁有阻塞模式和非阻塞模式,可以實現非同步IO,下面分別給出IO伺服器和NIO伺服器的一個例子:

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();
        }
    }
}
解釋下以上程式碼中的一些概念: ServerSocket:Socket連線的伺服器類,可以監聽給定埠的Socket連線; accept函式:在沒有新的Socket連線時會阻塞,一旦有新連線即返回。

本地啟動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連線的輸入列印到控制檯,因為程式碼中把兩處會阻塞的地方都設定為非阻塞模式,然後主執行緒掃描各個連線 的輸入,再將其輸出到控制檯,由於非阻塞特性,主執行緒就可以在監聽連線的同時還可以監聽已有連線的輸入,對於多連線少輸入的系統,這種處理方式是非常經濟的。