Writing in the front
A lot of you are a little bit confused by the Java IO model, sometimes there are 4 models, sometimes there are 5 models.
Java NIO is not a non-blocking IO, and Java NIO is a non-blocking IO.
A lot of people are confused by asynchrony and non-blocking. It’s not blocking, isn’t it asynchronous? This is this, this is so hard.
This article, from the bottom of the start, to your friends, from the bottom, the Java four IO model. For those of you who need an interview, or who haven’t figured it out yet, you are thoroughly blessed.
1.1. Java IO Read and write principles
Whether Socket read and write or file read and write, application development in Java level or Linux system bottom development, all belong to input and output processing, referred to as IO read and write. In principle and process, are consistent. The difference is in parameters.
User programs to read and write IO, basically will use read and write two major system calls. Different operating systems may have different names, but the functions are the same.
Let’s start with the basics: A read system call does not read data directly from a physical device into memory. Write system calls also don’t write data directly to physical devices.
The read system call copies data from the kernel buffer to the process buffer. Write system calls, on the other hand, copy data from the process buffer to the kernel buffer. Neither system call is responsible for exchanging data between the kernel buffer and disk. The underlying read/write exchange is completed by the operating system kernel.
1.1.1. Kernel and process buffers
The purpose of the buffer is to reduce the frequency of system IO calls. As we all know, the system call needs to save the previous process data and state information, and after the call to return to restore the previous information, in order to reduce the loss of time, but also the loss of performance of the system call, so the emergence of buffer.
With buffers, the operating system uses the read function to copy data from the kernel buffer to the process buffer, and write to copy data from the process buffer to the kernel buffer. Wait until the buffer reaches a certain number before invoking I/O to improve performance. The kernel decides when to read and store, and the user program does not need to care.
In Linux, the system kernel also has a buffer called the kernel buffer. Each process has its own separate buffer, called the process buffer.
So, the user program’s IO read-write program, for the most part, is not actually doing IO operations, but is reading and writing its own process buffer.
1.1.2. Java IO reading and writing process
Read copies data from the kernel buffer to the process buffer, and write copies data from the process buffer to the kernel buffer. They are not equivalent to the exchange of data between the kernel buffer and disk.
Insert a picture description here
Let’s start by looking at a typical process for a typical Java server to handle a network request:
(1) Client request
Linux reads the interrupted request data from the customer through the nic and reads the data into the kernel buffer.
(2) Obtain the requested data
The server reads data from the kernel buffer into the Java process buffer.
(1) Server-side business processing
The Java server handles client requests in its own user space.
(2) The server returns data
The Java server has constructed a response that is written from the user buffer to the system buffer.
(3) Send it to the client
The Linux kernel writes the data in the kernel buffer to the nic through network I/O. The NIC sends the data to the target client through the underlying communication protocol.
1.2. Four main IO models
Server-side programming often needs to construct high-performance IO models. There are four common IO models:
(1) Blocking IO (Blocking IO)
First, explain blocking and non-blocking here:
Blocking I/O means that the kernel does not return to user space until the I/O operation is complete. Blocking refers to the execution state of a user-space program that waits until the IO operation completes. The traditional IO model is synchronous blocking IO. In Java, sockets created by default are blocked.
Second, explain synchronous and asynchronous:
Synchronous IO is a way to initiate calls in user space and kernel space. Synchronous I/O means that the user space thread is the active one and the kernel space is the passive one. Asynchronous I/O, on the other hand, refers to the kernel that actively initiates I/O requests and the user thread that passively receives them.
(2) Sync non-blocking IO
Non-blocking I/O: The user program does not need to wait for the kernel to complete the I/O operation and the kernel immediately returns a status value to the user. The user space can immediately return to the user space and execute the user operation without waiting for the kernel to complete the I/O operation.
To put it simply: blocking is when the user space (the calling thread) waits and does nothing else; Non-blocking means that the user space (calling thread) gets the state and returns it, and the IO operation can do it, can’t do it, can’t do it.
Non-blocking I/O requires that the socket be set to NONBLOCK.
To emphasize, the NIO (synchronous non-blocking IO) model is not the NIO (New IO) library for Java.
(3) IO Multiplexing
The classic Reactor design pattern, sometimes called asynchronous blocking IO, is used by Selector in Java and epoll in Linux.
(4) Asynchronous IO
Asynchronous IO refers to the way that user space and kernel space are called in reverse. User-space threads become passive receivers and kernel space is active callers.
This is somewhat similar to the typical pattern in Java, which is the callback mode. The user space thread registers various IO events to the kernel space callback function, which is called by the kernel.
1.3. Blocking IO (Blocking IO)
In Java processes on Linux, all sockets are blocking IO by default. In the blocking I/O model, the application blocks from the IO system call until the system call returns. Upon success, the application process begins processing cached data in user space.
Insert a picture description here
For example, to initiate a blocking socket read operating system call, the process might look something like this:
(1) When the user thread invokes the read system call, the kernel begins the first stage of IO: preparing data. In many cases, data has not arrived in the first place (i.e., a complete Socket packet has not been received), and the kernel waits for enough data to arrive.
(2) When the kernel waits until the data is ready, it copies the data from the kernel buffer to the user buffer (user memory), and the kernel returns the result.
(3) The user thread enters a blocking state from the start of the READ system call to the IO read. The user thread is not released from the block state until the kernel returns the result.
So, the characteristic of blocking IO is that during both phases of IO execution in the kernel, the user thread is blocked.
Advantages of BIO:
Simple procedure, while blocking waiting for data, the user thread hangs. User threads consume very little CPU resources.
Disadvantages of BIO:
Typically, there is a separate thread for each connection, or a thread that maintains reads and writes of a connected IO stream. In the case of small concurrency, this is fine. However, in high concurrency scenarios, when a large number of threads are required to maintain a large number of network connections, the overhead of memory and thread switching can be huge. Therefore, basically, the BIO model is not available in high concurrency scenarios.
1.4. Synchronizing non-blocking NIO (None Blocking IO)
On Linux, you can set the socket to make it non-blocking. Two things happen to an application in the NIO model once it starts IO system calls:
(1) In the case of no data in the kernel buffer, the system call will immediately return, returning a call failure message.
(2) In the case of data in the kernel buffer, is blocked until the data is copied from the kernel buffer to the user process buffer. After the replication is complete, the system call returns success and the application process begins processing cached data in user space.
Insert a picture description here
Here’s an example. To initiate a non-blocking socket read operating system call, the flow looks like this:
(1) In the stage where the kernel data is not ready, the user thread will immediately return the IO request. User threads need to make IO system calls constantly.
(2) After the kernel data arrives, the user thread initiates a system call, and the user thread blocks. The kernel starts copying data. It copies the data from the kernel buffer to the user buffer (user memory), and the kernel returns the result.
(3) The user thread removes the block state and runs again. After many attempts, the user thread actually reads the data and continues to execute.
NIO features:
The threads of the application need to continuously make I/O system calls, poll to see if the data is ready, and if not, continue polling until the system call completes.
NIO’s advantages:
Each IO system call made can be returned immediately while the kernel waits for data. User thread will not block, real-time performance is good.
NIO’s disadvantages:
IO system calls need to be repeatedly initiated. This constant polling will constantly ask the kernel, which will consume a lot of CPU time and reduce the utilization of system resources.
In short, the NIO model is also not available in high concurrency scenarios. Typically, Web servers do not use this IO model. This model is rarely used directly, but the non-blocking IO feature is used in other IO models. This IO model is also not involved in the actual development of Java.
Java NIO (New IO) is not the NIO model of the IO model, but another model called IO multiplexing.
IO multiplexing multiplex
How do you avoid the polling wait problem in the synchronous non-blocking NIO model? This is the IO multiplexing model.
The IO multiplexing model is a new system call in which a process can monitor multiple file descriptors. Once a descriptor is ready (usually kernel buffer readable/writable), the kernel kernel can tell the program to make the corresponding IO system call.
Currently support IO multiplexing system call, select, epoll and so on. Select system call, which is currently supported on almost all operating systems, has good cross-platform characteristics. Epoll was introduced in the Linux 2.6 kernel as a Linux enhanced version of the SELECT system call.
The basic principle of the IO multiplexing model is the select/epoll system call, in which a single thread continuously polls hundreds of socket connections for which the SELECT /epoll system call is responsible, and returns those read-write connections when data arrives on one or more socket connections. Thus, the benefits are obvious — one or even hundreds of network connections that can be read or written can be queried with a single SELECT /epoll system call.
Here’s an example. To initiate a multiplexed IO read operating system call, the process looks like this:
Insert a picture description here
In this pattern, instead of a READ system call, a SELECT /epoll system call is made first. Of course, there is a prerequisite to pre-register the target network connection into the select/epoll queryable socket list. Then, you can start the entire IO multiplexing model read process.
(1) Perform select/epoll system call to query read connections. The kernel queries the list of all selectable sockets, and select returns when the data in any socket is ready.
When the user process calls select, the entire thread is blocked.
(2) After the user thread obtains the target connection, it initiates a read system call, and the user thread blocks. The kernel starts copying data. It copies the data from the kernel buffer to the user buffer (user memory), and the kernel returns the result.
(3) The user thread removes the block state, and the user thread finally reads the data and continues to execute.
Multiplex IO features:
The IO multiplexing model is based on the select/epoll system call which can be provided by the kernel. Multiplexing IO requires two system calls, a SELECT /epoll query call and a read call to the IO.
Like the NIO model, multiplexing IO requires polling. The thread in charge of the SELECT /epoll query calls needs to continuously perform select/epoll polling to find connections that can perform I/O operations.
In addition, the multiplexing IO model is related to the previous NIO model. For each socket that can be queried, it is generally set to a non-blocking model. Only this point is transparent to the user program.
Advantages of multiplexing IO:
The advantage of using SELECT /epoll is that it can handle thousands of connections simultaneously. The biggest advantage of I/O multiplexing over maintaining a connection with one thread is that the system does not have to create or maintain threads, which greatly reduces the overhead of the system.
Java NIO (New IO) technology, the use of IO multiplexing model. On Linux systems, the epoll system call is used.
Disadvantages of multiplexing IO:
In essence, a select/epoll system call, which is a synchronous IO, is also a blocking IO. Both need to be responsible for reading and writing after the read and write event is ready, that is, the read and write process is blocked.
How do you adequately unblock a thread? That’s the asynchronous IO model.
1.6. Asynchronous IO Model
How to further improve efficiency and unblock that last block? This is the asynchronous I/O model, called asynchronous I/O for short.
The basic flow of AIO is as follows: The user thread informs the kernel to start an I/O operation through a system call, and the user thread returns. Kernel After I/O operations (including data preparation and replication) are complete, the kernel notifies the user of the program and enables the user to perform subsequent operations.
Kernel data preparation is to read data from network physical devices (network cards) into the kernel buffer. Kernel data replication is the copying of data from the kernel buffer to the buffer of user program space.
Insert a picture description here
(1) When the user thread invokes the read system call, it can immediately start doing other things. The user thread does not block.
(2) The kernel begins the first stage of IO: preparing data. When the kernel waits until the data is ready, it copies the data from the kernel buffer to the user buffer (user memory).
(3) The kernel sends a signal to the user thread, or calls back to the callback interface registered by the user thread to tell the user thread that the read operation is complete.
(4) The user thread reads the data of the user buffer to complete the subsequent business operations.
Characteristics of asynchronous IO model:
User threads are not blocked in either the waiting or copying phases of the kernel. The user thread needs to receive the kernel’s IO completion event, or register the IO completion callback function, into the operating system kernel. So asynchronous IO is sometimes called signal-driven IO.
Disadvantages of the asynchronous IO model:
Need to complete the event registration and transfer, here side needs the underlying operating system to provide a lot of support, to do a lot of work.
Currently, true asynchronous I/O is implemented on Windows systems through IOCP. However, in its current form, Windows is rarely used as a server operating system for millions or more concurrent applications.
In Linux, the asynchronous IO model was introduced in version 2.6 and is not perfect at present. Therefore, this is also under Linux, the implementation of high concurrency network programming is based on IO reuse model mode.
To summarize:
Four IO models, theoretically, the further back, the less blocking, the efficiency is also optimal. Of the four I/O models, the first three are synchronous I/O because the actual I/O operations block the thread. Only the last is the true asynchronous I/O model, but the Linux operating system is not perfect yet.
You got it, cousin
From the network