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“跟服务器断开连接:

Java NIO学习总结一(非阻塞特性)_第1张图片

可以使用多个窗口开启多个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连接的输入打印到控制台,因为代码中把两处会阻塞的地方都设定为非阻塞模式,然后主线程扫描各个连接 的输入,再将其输出到控制台,由于非阻塞特性,主线程就可以在监听连接的同时还可以监听已有连接的输入,对于多连接少输入的系统,这种处理方式是非常经济的。



你可能感兴趣的:(【Java】学习记录)