Java IO Model Classification

Java IO Model Classification

There are three distinct I/O models in Java: AIO, BIO, and NIO.

  1. BIO (Blocking I/O: Synchronous blocking I/O model). In the BIO model, after an application initiates an I/O operation, it will block continuously until the I/O operation completes; no other tasks can be performed during this period. The application can only continue execution once the I/O operation is finished. BIO was one of Java's early I/O APIs and utilized traditional synchronous I/O mechanisms.

    • BIO is a traditional blocking-style I/O model that operates in terms of streams. Each connection requires an independent thread to handle it; when a thread performs an I/O operation, it blocks continuously until the data is ready, leading to waste of threads. BIO is suitable for scenarios with relatively few connections.
  2. NIO (Non-blocking I/O: Synchronous non-blocking I/O model). In the NIO model, after an application initiates an I/O operation, it does not need to wait for the completion of the I/O operation and can continue executing other tasks. However, when checking the status of whether the I/O operation is complete, blocking waiting still needs to be performed until the I/O operation completes or data becomes readable. NIO was a new I/O API introduced in Java 1.4, utilizing an event-driven I/O mechanism based on events.

    • NIO is an event-driven I/O model; it handles multiple connections through a single thread, thereby avoiding waste of threads. When there is readable data for a connection, an event will be triggered to notify the thread, allowing the thread to process the data for that connection. NIO is suitable for scenarios with many connections.
  3. AIO (Asynchronous I/O: Asynchronous non-blocking I/O model). In the AIO model, after an application initiates an I/O operation, it does not need to wait for the completion of the I/O operation but can continue executing other tasks. Once the I/O operation is completed, the operating system will notify the application to proceed with subsequent processing.

    • AIO was a new I/O API introduced in Java 7 and utilizes asynchronous I/O mechanisms provided by the OS. AIO is an asynchronous I/O model introduced in Java 7; it is based on NIO but more advanced compared to NIO, capable of implementing asynchronous read/write operations. When an I/O operation completes, a callback function will be triggered, enabling asynchronous processing. AIO is suitable for scenarios with extremely high connection counts, long connection durations, and small data interaction volumes.

Compared to the BIO model, both AIO and NIO models can support higher concurrency performance and lower system overhead because they utilize operating system's asynchronous I/O mechanisms or event-driven mechanisms to allow applications to make better use of CPU resources and I/O resources. However, the programming models for AIO and NIO are more complex than that of BIO and require a deeper understanding and mastery.

In summary:

  • BIO is suitable for scenarios with relatively few connections;
  • NIO is suitable for scenarios with many connections;
  • AIO is suitable for scenarios with extremely high connection counts, long connection durations, but small data interaction volumes. In practical applications, the appropriate I/O model should be selected based on specific business scenarios.

Introduction to BIO

Synchronous blocking I/O model. In the BIO model, after an application initiates an I/O operation, it will block continuously until the I/O operation completes; no other tasks can be performed during this period. The application can only continue execution once the I/O operation is finished. BIO was one of Java's early I/O APIs and utilized traditional synchronous I/O mechanisms.

BIO is a traditional blocking-style I/O model that operates in terms of streams. Each connection requires an independent thread to handle it; when a thread performs an I/O operation, it blocks continuously until the data is ready, leading to waste of threads. BIO is suitable for scenarios with relatively few connections.

Example of BIO

import java.io.*;
import java.net.*;

public class BIOExample {
    public static void main(String[] args) throws IOException {
        // Create a ServerSocket object listening on port 8888
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("Server started on port 8888.");

        while (true) {
            // Wait for client connection
            Socket clientSocket = serverSocket.accept();
            System.out.println("Client connected from " + clientSocket.getRemoteSocketAddress());

            // Read data from the client using BufferedReader and InputStreamReader
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            String message = in.readLine();
            System.out.println("Received message from client: " + message);

            // Send response data to the client via PrintWriter
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            out.println("Hello, client!");

            // Close connection
            clientSocket.close();
        }
    }
}

This is an example of BIO where:

  • The ServerSocket object creates a server socket listening on port 8888;
  • Blocking waiting for the client connection is performed via the serverSocket.accept() method;
  • Data reading from the client uses BufferedReader and InputStreamReader, while sending response data to the client uses PrintWriter;
  • Finally, the connection is closed.

NIO

Introduction to NIO

Synchronous non-blocking I/O model. In the NIO model, after an application initiates an I/O operation, it does not need to wait for the completion of the I/O operation and can continue executing other tasks. However, when checking whether the I/O operation is complete, blocking waiting still needs to be performed until the I/O operation completes or data becomes readable. NIO was a new I/O API introduced in Java 1.4, utilizing an event-driven I/O mechanism based on events.

NIO is an event-driven I/O model; it handles multiple connections through a single thread, thereby avoiding waste of threads. When there is readable data for a connection, an event will be triggered to notify the thread, allowing the thread to process the data for that connection. NIO is suitable for scenarios with many connections.

Example of NIO

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;

public class NIOExample {
    public static void main(String[] args) throws IOException {
        // Create a ServerSocketChannel object listening on port 8888
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8888));
        serverSocketChannel.configureBlocking(false);
        System.out.println("Server started on port 8888.");

        // Create a Selector object to listen for events
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // Wait for an event to occur
            selector.select();

            for (SelectionKey key : selector.selectedKeys()) {
                if (key.isAcceptable()) {
                    // Accept the client connection
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = serverChannel.accept();
                    clientChannel.configureBlocking(false);
                    clientChannel.register(selector, SelectionKey.OP_READ);

                    System.out.println("Client connected from " + clientChannel.getRemoteAddress());
                } else if (key.isReadable()) {
                    // Read data from the client
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = clientChannel.read(buffer);

                    if (bytesRead > 0) {
                        buffer.flip();
                        byte[] bytes = new byte[buffer.remaining()];
                        buffer.get(bytes);
                        String message = new String(bytes, "UTF-8");
                        System.out.println("Received message from client: " + message);

                        // Send response data to the client
                        ByteBuffer responseBuffer = ByteBuffer.wrap("Hello, client!".getBytes("UTF-8"));
                        clientChannel.write(responseBuffer);
                    } else if (bytesRead < 0) {
                        clientChannel.close();
                    }
                }
            }
            selector.selectedKeys().clear();
        }
    }
}

This is an example of NIO where:

  • The ServerSocketChannel object creates a server socket listening on port 8888;
  • Events are listened to via the Selector object;
  • Event representation uses the SelectionKey class, including OP_ACCEPT and OP_READ events;
  • In an OP_ACCEPT event, accept the client connection and register the client's SocketChannel with the Selector to listen for read events;
  • In an OP_READ event, read data from the client and send response data back.

AIO

Introduction to AIO

Asynchronous non-blocking I/O model. In the AIO model, after an application initiates an I/O operation, it does not need to wait for the completion of the I/O operation but can continue executing other tasks. Once the I/O operation is completed, the operating system will notify the application to proceed with subsequent processing.
AIO was a new I/O API introduced in Java 7 and utilizes asynchronous I/O mechanisms provided by the OS. AIO is an asynchronous I/O model introduced in Java 7; it is based on NIO but more advanced compared to NIO, capable of implementing asynchronous read/write operations. When an I/O operation completes, a callback function will be triggered, enabling asynchronous processing.
AIO is suitable for scenarios with extremely high connection counts, long connection durations, and small data interaction volumes.

Example of AIO

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class AIOExample {
    public static void main(String[] args) throws IOException {
        // Create an AsynchronousServerSocketChannel object listening on port 8888
        AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8888));

        System.out.println("Server started on port 8888.");

        // Register Accept event handler
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, IOException>() {
            @Override
            public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
                // Client connection accepted successfully; register Read event for the channel
                ByteBuffer buffer = ByteBuffer.allocate(1024);

                serverChannel.accept(null, this);

                clientChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer attachment) {
                        if (result > 0) {
                            // Data read successfully; flip the buffer to prepare for reading as a sequence of bytes
                            attachment.flip();

                            byte[] bytes = new byte[attachment.remaining()];
                            attachment.get(bytes);
                            String message = new String(bytes, "UTF-8");
                            System.out.println("Received message from client: " + message);

                            // Send response data to the client asynchronously
                            ByteBuffer responseBuffer = ByteBuffer.wrap("Hello, client!".getBytes("UTF-8"));
                            
                            clientChannel.write(responseBuffer, responseBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                                @Override
                                public void completed(Integer result, ByteBuffer attachment) {
                                    if (attachment.hasRemaining()) {
                                        // Continue writing remaining data in chunks until complete
                                        clientChannel.write(attachment, attachment, this);
                                    }
                                }

                                @Override
                                public void failed(Throwable exc, ByteBuffer attachment) {
                                    System.out.println("Failed to send response to client.");
                                }
                            });
                        } else if (result < 0) {
                            // End of stream reached; close the channel gracefully
                            try {
                                clientChannel.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        System.out.println("Failed to read data from client.");
                    }
                });
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("Failed to accept client connection.");
            }
        });

        // Block the main thread until completion (optional in some contexts but ensures program termination here)
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

This is an example of AIO where:

  • The AsynchronousServerSocketChannel object creates a server socket listening on port 8888;
  • Event handling uses the CompletionHandler callback function, including Accept and Read events;
  • In an Accept event, accept the client connection and register its AsynchronousSocketChannel to listen for read events (note: in this specific implementation pattern shown, it re-registers or chains operations);
  • In a Read event, read data from the client and send response data back asynchronously using CompletionHandler.