Search code examples
javamultithreadingnio

Is JAVA NIO wasting CPU cycles by looping continuously?


Code for an echo server using a single threaded java I/O

public static void main(String[] args) throws Exception {

    // create socket
    int port = 4444;
    ServerSocket serverSocket = new ServerSocket(port);
    System.err.println("Started server on port " + port);

    try {

        // repeatedly wait for connections, and process
        while (true) {

            // a "blocking" call which waits until a connection is requested
            Socket clientSocket = serverSocket.accept();
            System.err.println("Accepted connection from client");

            // open up IO streams
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            PrintStream out = new PrintStream(clientSocket.getOutputStream());

            // waits for data and reads it in until connection dies
            // readLine() blocks until the server receives a new line from client
            String s;
            while ((s = in.readLine()) != null) {
                out.print(s);
            }

            // close IO streams, then socket
            System.err.println("Closing connection with client");
            out.close();
            in.close();
            clientSocket.close();
        }
    } finally {
        serverSocket.close();
    }

}

code for the same using NIO

public static void main(String[] args) throws IOException {
    ServerSocketChannel server = ServerSocketChannel.open();
    server.socket().bind(new InetSocketAddress(PORT_NUMBER));
    server.socket().setReuseAddress(true);
    server.configureBlocking(false);

    Selector selector = Selector.open();
    server.register(selector, SelectionKey.OP_ACCEPT);

    ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
    while (true) {
        int channelCount = selector.select();
        if (channelCount > 0) {
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isAcceptable()) {
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ, client.socket().getPort());
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    System.out.println("port: " + key.attachment());
                    if (client.read(buffer) < 0) {
                        key.cancel();
                        client.close();
                    } else {
                        buffer.flip(); // read from the buffer
                          /*
                           * byte[] received = new byte[buffer.remaining()];
                           * buffer.get(received); buffer.clear(); // write into the buffer
                           * buffer.put(received); buffer.flip(); // read from the buffer
                           */
                        client.write(buffer);
                        buffer.clear(); // write into the buffer
                    }
                }
            }
        }
    }
}

Here using normal I/O the main thread comes and wait at the socket.accept() call. But NIO doesn't do that since socketChannel.accept() is not a blocking call.

So wont the NIO program be continuously running the loop ? And result in waste of CPU cycles ? Can I write the program in a better way. Sorry I am very new to JAVA NIO and asynchronous programming.


Solution

  • In normal IO the thread is blocked on serverSocket.accept().

    With NIO the thread is blocked on selector.select().

    From the JavaDoc of Selector#select():

    This method performs a blocking selection operation.


    Why is this called "non blocking IO"?

    Actually, your first example (with normal IO) has two blocking calls: server.accept() and in.readLine().

    Now consider the case with a badly behaving client: it opens a connection to the server, but never sends any data. With normal IO the server thread waits in in.readLine() for data to arrive and cannot serve any other client until the first client closes its connection.

    With NIO the picture is different: if a client opens a connection, the server thread wakes up, server.accept()s the connection and registers the SocketChannel with the same selector. Then the server thread waits on the selector once more through selector.select(). Now there are two possibilities to wake up the server thread: either another client connecting, or the first client sending some data.

    So the term "non blocking IO" does not mean that the server thread is never blocked - it means that a non-behaving client cannot block the server thread forever.