The difference between BIO, NIO, and AIO

Many articles just throw a bunch of definitions and some vivid examples when talking about BIO, NIO, and AIO. Seems to be well understood. However, the most basic essential principles are not revealed. If you do not start from the principle of IO, it is difficult to understand the difference between the three. In this article, you will get: the performance difference between synchronous/asynchronous + blocking/non-blocking; the difference between BIO, NIO, and AIO; understanding and implementing multiplexing when NIO operates Sockets; at the same time, master the bottom and core operation skills of IO.

IO principle in Java

First of all, IO in Java depends on the operating system kernel. 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. The network card receives the network data transmitted through the network cable, and writes the network data to the memory.
  2. 2When the network card writes the data into the memory, the network card sends an interrupt signal to the cpu, and the operating system can know that there is new data coming, and then process the data through the network card interrupt program.
  3. Write the network data in the memory to the receive buffer of the corresponding socket.
  4. After the data in the receive buffer is written, the application starts data processing.

A simple example of socket code corresponding to abstraction to java is as follows:

It can be seen that this process is very similar to the network IO of the underlying kernel, which is mainly reflected in accept() waiting for a request from the network, and then the bytes[] array is used as a buffer to wait for the data to be filled before processing. 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 and asynchronous, blocking and non-blocking.

Synchronous and Asynchronous

Synchronization and asynchrony: Synchronization means that when the completion of a task needs to depend on another task, the dependent task can be completed only after waiting for the dependent task to complete. This is a reliable task sequence. Either all successes succeed, and all failures fail, and the status of the two tasks can be kept the same. Asynchronous means that there is no need to wait for the dependent task to complete, but only to notify the dependent task of what work to complete, and the dependent task is executed immediately, as long as the entire task is completed by itself. As for whether the dependent task is actually completed in the end, the task that depends on it cannot be determined, so it is an unreliable task sequence. We can use phone calls and text messages to describe synchronous versus asynchronous operations well.

Synchronization and asynchrony refer to whether each method in an execution flow must depend on the completion of the previous method before it can continue to execute. Suppose our execution process is: method one and method two in turn.

Synchronization means that once the call has started, the caller must wait until the method call returns before continuing the subsequent behavior. That is, method 2 must wait until method 1 is executed before it can be executed.

Asynchronous means that the call returns immediately, and the caller can continue the subsequent behavior without waiting for the execution of the code in the method to end. (The code in the specific method may be called back after it is executed by another thread.) That is, when method 1 is executed, it is directly handed over to other threads for execution, and it is not executed by the main thread, so it will not block the main thread, so method 2 does not have to wait until method 1 is completed to start execution.

Synchronization and asynchrony focus on whether the executor of the method is the main thread or another thread. The main thread needs to wait for the method execution to complete, and other threads do not need to wait for the method call to return immediately, and the main thread can directly execute the next code.

Synchronous and asynchronous is to achieve efficiency difference from the coordination between multiple threads.

Why do you need asynchrony? The author believes that the essence of asynchrony is to solve the blocking of the main thread, so many discussions on the Internet have made four combinations of synchronous asynchronous and blocking non-blocking. One of them has the situation of asynchronous blocking. If asynchronous is also blocking? So why do you want to perform asynchronous operations specifically?

Blocking and Non-Blocking
Blocking and non-blocking: Blocking and non-blocking are mainly in terms of CPU consumption. Blocking means that the CPU stops and waits for a slow operation to complete the CPU before completing other things. Non-blocking means that the CPU does other things while the slow operation is executing, and when the slow operation is completed, the CPU then completes the subsequent operations. Although the non-blocking method on the surface can significantly improve the utilization of the CPU, it also brings another consequence that the thread switching of the system increases. Whether the increased CPU usage time can compensate for the switching cost of the system needs to be carefully evaluated.

Blocking and non-blocking refer to whether to do nothing in place when a single thread encounters a synchronous wait.

Blocking means that after encountering a synchronization wait, it has been waiting for the synchronization method to complete the processing in place.

Non-blocking refers to encountering synchronous waiting, not waiting in place, do other operations first, and then observe whether the synchronization method is completed after a time interval.

Blocking and non-blocking concern whether the thread is waiting in place.

The author believes that blocking and non-blocking can only be combined with synchronization. Asynchronous is naturally non-blocking, and this non-blocking is for the main thread. (Some people may think that putting a blocking operation in an asynchronous method is asynchronous blocking, but think about it, it is precisely because it is a blocking operation that it is put into an asynchronous method, do not block the main thread)

Example to explain
Haidilao is delicious, but there are often queues. Let’s take this example from life to explain.

Customer A went to eat Haidilao and just sat and waited for an hour before eating hot pot. (BIO)
Customer B went to eat Haidilao. He saw that he had to wait for a long time, so he went to the mall, and every time he went shopping for a while, he ran back to see if he was in line. So he ended up shopping and eating Haidilao. (NIO)
Customer C went to eat Haidilao. Since he is a senior member, the store manager said, you can go to the mall to play casually, and I will call you immediately when there is a seat. So customer C doesn’t have to sit and wait, and don’t have to run back every once in a while to see if they can wait, and finally eat Haidilao (AIO)
Which way is more efficient? Is it obvious at a glance?

BIO is a traditional package. It is implemented based on the stream model. The interaction method is synchronous and blocking. That is to say, when reading the input stream or output stream, the thread will be blocked until the read and write actions are completed. There, calls between them are in a reliable linear order. Its advantage is that the code is relatively simple and intuitive; the disadvantage is that the efficiency and scalability of IO is very low, and it is easy to become an application performance bottleneck.

The full name of BIO is Blocking IO, which is the traditional IO model before JDK1.4, and itself is a synchronous blocking mode. After the thread initiates an IO request, it keeps blocking the IO until the buffer data is ready, and then goes to the next step. For network communication, it is a request-response method. Although the upper-layer application development is simplified, there is a huge bottleneck in terms of performance and reliability. Imagine if each request needs to create a new thread for special processing, then in high In a concurrent scenario, machine resources are quickly exhausted.

NIO is the java.nio package introduced in Java 1.4. It provides new abstractions such as Channel, Selector, Buffer, etc. It can build multiplexed, synchronous non-blocking IO programs, and at the same time provides high-performance data operations that are closer to the underlying operating system. Way.

NIO, also called Non-Blocking IO, is a synchronous non-blocking IO model. After the thread initiates an io request, it returns immediately (non-blocking io). Synchronization means that you must wait for the data in the IO buffer to be ready. Non-blocking means that the user thread does not wait for the IO buffer in place. You can do some other operations first, but you need to poll regularly to check whether the IO buffer data is ready. .

NIO in Java means new IO. In fact, it is NIO plus IO multiplexing technology. Ordinary NIO is thread polling to see if an IO buffer is ready, while new IO in Java refers to threads polling to see which ones are ready in a bunch of IO buffers, which is an idea of ​​IO multiplexing . In the IO multiplexing model, the task of checking whether the IO data is ready is handed over to the system-level select or epoll model, which is monitored by the system to reduce the burden on user threads.

NIO mainly integrates three technologies: buffer, channel, and selector. Data is obtained through zero-copy buffer, and each client registers on the selector (multiplexer) through channel. The server continuously polls the channel to obtain information about the client. There are four status flags on the channel: connect, accept (blocking), read (readable), and write (writable). Follow up with the identification. So a server can receive an infinite number of channels. No need to open a new thread. Greatly improved performance.

AIO is a package introduced after Java 1.7. It is an upgraded version of NIO. It provides an asynchronous non-blocking IO operation mode, so people call it AIO (Asynchronous IO). Asynchronous IO is implemented based on events and callback mechanisms, that is, application operations. After that, it will return directly and will not be blocked there. When the background processing is completed, the operating system will notify the corresponding thread to perform subsequent operations.

AIO is a true asynchronous non-blocking IO model. In the above NIO implementation, the user thread needs to poll regularly to check whether the IO buffer data is ready, which occupies the application thread resources. In fact, polling is equivalent to blocking, and does not really liberate the current thread, because it still needs to query which IO ready. The real ideal asynchronous non-blocking IO should let the kernel system complete, and the user thread only needs to tell the kernel that when the buffer is ready, notify me or execute the callback function I handed you.

AIO can perform real asynchronous operations, but it is more complicated to implement. There are very few operating systems that support pure asynchronous IO. Currently, windows are implemented by IOCP technology, while on Linux, the bottom layer is still implemented by epoll.