Many articles talk about BIO, NIO, AIO and just throw out a bunch of definitions and some vivid examples. That seems pretty straightforward. But it does not show the most basic essential principle, and it is difficult to understand the difference between the three without starting from the principle of IO. So this article starts with an analysis of how Java performs IO operations.

1. I/O principles in Java

According to the von Neumann structure, the computer structure is divided into five parts: arithmetic unit, controller, memory, input device and output device.

Input devices (such as keyboards) and output devices (such as displays) are external devices. Network cards and hard disks can be input devices or output devices. The input device inputs data to the computer, and the output device receives data output by the computer.

From the perspective of computer architecture, I/O describes the process of communication between a computer system and external devices.

To ensure the stability and security of the operating system, the address space of a process is divided into User space and Kernel space.

Applications like ours run in user space, and only kernel space can perform system-level resource-related operations such as file management, process communication, memory management, and so on. In other words, if we want to do IO operations, we must depend on the capacity of the kernel space. Also, programs in user space cannot access kernel space directly. When you want to perform I/O operations, you can only invoke the system to request the operating system for help because you do not have the permission to perform these operations.

Therefore, if a user process wants to perform IO operations, it must access the kernel space indirectly through a system call

Disk IO (reading and writing files) and network IO (network requests and responses) are the ones we encounter most during normal development.

When an application makes an I/O call, it goes through two steps:

  1. The kernel waits for the I/O device to prepare data
  2. The kernel copies data from kernel space to user space.

First of all, THE IO in Java is dependent on the operating system kernel, and the IO reading and writing in our program actually calls the two system calls of read and write in the operating system kernel.

How does the kernel interact with IO?

  1. When the nic receives network data from the network cable, it writes the network data to the memory.
  2. When the nic data is written to the memory, the NIC to the CPU issued an interrupt signal, the operating system will know that there is new data arrival, and then through the nic interrupt program to deal with the data.
  3. Writes network data in memory to the receive buffer of the corresponding socket.
  4. When the data for the receive buffer is written, the application begins to process the data.

The following is a simple example of socket code abstracted to Java:

Public class SocketServer {public static void main(String[] args) throws Exception {// Monitor the specified port int port = 8080; ServerSocket server = new ServerSocket(port); Socket Socket = server.accept(); InputStream InputStream = socket.getinputStream (); byte[] bytes = new byte[1024]; int len; while ((len = inputStream.read(bytes)) ! String message = new String(bytes, 0, len,"UTF-8"); } // socket, server, stream close operation, omit not table}}Copy the code

You can see that this process is very similar to the network IO of the underlying kernel, in that accept() waits for requests from the network to arrive and then the Bytes [] array acts as a buffer waiting for data to fill up. The difference between BIO, NIO, and AIO is whether these operations are synchronous or asynchronous, blocking or non-blocking.

So we introduce the concepts of synchronous asynchrony, blocking and non-blocking.

2. Synchronous and asynchronous

Synchronous and asynchronous refers to whether each method in an execution process must depend on the completion of the previous method before execution can continue.

Suppose our execution flow is: method 1 and method 2 in order.

  • Synchronization: Refers to the fact that once the call has started, the caller must wait until the method call returns before continuing with the subsequent behavior. Method two must wait until method one has finished executing.
  • Asynchronous: When the call returns immediately and the caller does not have to wait for the code execution within the method to finish before continuing with the subsequent behavior. Callbacks may occur after the code in a specific method is handed off to another thread. That is, when method 1 is executed, it is directly handed over to another thread, not the main thread, so it does not block the main thread, so method 2 does not have to wait for method 1 to complete.

Synchronous and asynchronous are concerned with whether the method is executed by the main thread, which waits for the method to complete, or another thread, which can execute the following code without waiting for the method to return immediately.

Synchronization and asynchrony achieve efficiency differences through coordination between multiple threads.

Why asynchrony?

The author thinks that the essence of asynchrony is to solve the blocking of the main thread, so there are many discussions on the Internet about the four combinations of synchronous asynchrony and blocking non-blocking, one of which has asynchronous blocking. What if asynchrony is also blocking? So why do asynchronous operations specifically?

3. Blocking and non-blocking

Blocking and non-blocking refers to whether a single thread does nothing in place when it encounters a synchronous wait.

  • Blocking refers to waiting in place for a synchronous method to complete processing after a synchronous wait is encountered.
  • Non-blocking means that when a synchronous wait is encountered, instead of waiting in place, other operations are performed first, and the time is cut off to observe whether the synchronous method is complete.

Blocking versus non-blocking concerns whether the thread is waiting in place.

4. I/O model in Java

4.1 BIO (Blocking I/O)

BIO belongs to the synchronous blocking IO model

In the synchronous blocking IO model, after an application makes a read call, it blocks until data is copied to user space in the kernel.

This is fine if the number of client connections is not high. However, when faced with hundreds of thousands or even millions of connections, the traditional BIO model is powerless. Therefore, we need a more efficient I/O processing model to handle the higher concurrency.

4.2 NIO (Non – blocking/New I/O)

NIO in Java was introduced in Java 1.4, corresponding to the java.nio package, which provides abstractions such as channels, selectors, and buffers. N in NIO can be interpreted as non-blocking, not just New. It supports buffer-oriented, channel-based approaches to I/O operations. For high-load, high-concurrency (network) applications, use NIO.

NIO in Java can be thought of as the I/O multiplexing model. NIO in Java is also widely considered a synchronous non-blocking IO model.

Follow my train of thought to look down, believe you will get the answer!

Let’s first look at the synchronous non-blocking IO model.

In the synchronous non-blocking IO model, applications make read calls and wait for data to be copied from kernel space to user space, while threads remain blocked until data is copied from kernel space to user space.

The synchronous non-blocking IO model is an improvement over the synchronous blocking IO model. Constant blocking is avoided through polling operations.

However, this IO model is also problematic: the application’s constant I/O system calls polling to see if the data is ready can be CPU intensive.

This is where the I/O multiplexing model comes in.

In the IO multiplexing model, the thread first makes a SELECT call to ask the kernel if the data is ready, and when the data is ready, the user thread makes a read call. The procedure for reading calls (data from kernel space -> user space) is still blocked.

Currently support IO multiplexing system call, select, epoll and so on. The SELECT system call is currently supported on almost all operating systems

  • Select call: System call provided by the kernel to query the available status of multiple system calls at once. Almost all operating systems support it.
  • Epoll call: The Linux 2.6 kernel is an enhanced version of the SELECT call, which optimizes IO execution efficiency.

IO multiplexing model reduces CPU resource consumption by reducing invalid system calls.

NIO in Java has a very important concept of a Selector, which can also be called a multiplexer. It allows you to manage multiple client connections with only one thread. When the client data arrives, it will be served.

4.3 AIO (Asynchronous I/O)

AIO is NIO 2. NIO 2, an improved version of NIO introduced in Java 7, is the asynchronous IO model.

Asynchronous IO is implemented based on events and callbacks, meaning that an action is applied and returned, not blocked, and when the background processing is complete, the operating system notifies the appropriate thread to proceed with the subsequent action.

At present, AIO is not widely used. Netty tried AIO before and abandoned it. This is because Netty’s performance on Linux has not improved much since AIO was adopted.

Finally, a diagram briefly summarizes BIO, NIO, and AIO in Java.

Related articles

  1. Juejin. Cn/post / 693984…
  2. www.cnblogs.com/2019wxw/p/1…