preface

In the last article, we learned about the concept of flows and the basic usage of JavaIO flows, but the evolution of JavaIO flows is not so simple, as attentive readers will discover, IO classes prior to JDK1.4 were based on blocking IO (you can see synchronized modified blocks in the inputStream.read () method implementation), With JDK1.4, NIO provided a mechanism for selector multiplexing along with channels and buffers, and the NIO upgrade to JDK1.7 provided a true asynchronous API……

Java network IO covers a wide range of knowledge, this article will briefly introduce the Java network IO related knowledge:

(If the article is not correct, or difficult to understand the place, please forgive, welcome correction)

Start with the operating system

To protect the security of the operating system, memory is divided into user space and kernel space. If a user wants to manipulate data in kernel space, the data needs to be copied from kernel space to user space.

For example, if the server receives a request from the client and wants to process it, it needs to go through these steps:

  • The server’s network driver receives the message, requests space from the kernel, and copies the complete packet to the kernel space after receiving it (this process is delayed because it may have been sent by packet).
  • Data is copied from kernel space to user space;
  • The user program processes it.

So we can think of the server receiving messages as two phases:

  • Wait for data to arrive
  • Copy data from kernel space to user space

IO in the operating system

The Linux operating system is used as an example. Linux is an operating system that treats all external devices as files. In its view: Everything is a file, then we treat all operations on external devices as operations on files. And when we read or write a file, we call the system call provided by the kernel.

In Linux, a basic IO involves two system objects: the process object that calls the IO (the user process) and the system kernel. That is, when a read operation occurs, it goes through these phases:

  • A read request is sent to the kernel through the read system call;
  • The kernel sends read instructions to the hardware and waits for the read to be ready.
  • DMA copies the data to be read into the specified kernel cache;
  • The kernel copies data from the kernel cache into user process space.

Several IO operations occur during this time:

  • Synchronous IO: When a user requests an IO operation, the kernel checks to see if the data to be read is ready. If not, it waits. During this time the user thread or memory is constantly polling for data to be ready. When the data is ready, it is copied from the kernel to user space.
  • Asynchronous I/O: The user thread only needs to send AN I/O request and receive an I/O completion notification. The KERNEL automatically completes the I/O operation and sends a notification to inform the user that the I/O operation has been completed. That is, in asynchronous IO, there is no blocking on the user thread.
  • Blocking IO: When a user thread initiates an IO request operation and the data to be manipulated by the kernel is not ready, the current thread is suspended and blocked waiting for the result to return.
  • Non-blocking IO: If the data is not ready, a flag message is returned to inform the user thread that the current data is not ready. There are other things the current thread can do while getting the result of this request.

Some readers may think, how synchronous IO, asynchronous IO and blocking IO, non-blocking IO operations are similar, why are they separated? In my opinion, synchronous, asynchronous and blocking and non-blocking are different ways of looking at the problem.

Synchronous and asynchronous

Synchronous and asynchronous are mainly from the point of view of message notification.

Synchronization is A reliable task queue in which task A depends on task B until task B is completed. Either they both succeed or they both fail, and the states of the two tasks remain the same.

Asynchronous means that task B does not need to wait for the completion of task B, but only notifies task B of the work to be completed, and task A executes immediately. As long as task A completes itself, the whole task is considered complete. There is no certainty that task A will actually complete task B, so this is an unreliable task queue.

For example, if small J wants to go to the bank counter, take the number to queue up. If he just stares at the number board and periodically asks if it’s his turn, that’s syncing; If he takes the number and makes a phone call, the teller will tell him to do business when he is in line, which is asynchronous. The difference between them is the way they wait for notifications.

Blocking and non-blocking

Blocking and non-blocking are primarily in terms of the state of waiting for message notifications.

Blocking means that the current thread is suspended until the result of the call is returned, waiting for a message notification and unable to perform other business. You can’t do anything else until the result of the call is returned.

Nonblocking corresponds to the concept of blocking, meaning that the function does not block the current thread until the result is not immediately available, but instead returns immediately. While the non-blocking approach seems to significantly improve CPU utilization, it can also increase thread switching on the system, and you need to evaluate whether the increased CPU execution time can step up the switching cost of the system.

If little J can do nothing but wait for a message to be notified, the mechanism is blocked. If he can talk on the phone while waiting, the state is non-blocking.

Synchronous, asynchronous, and blocking, non-blocking

Other readers may equate synchronization with blocking, but the two are not the same. For synchronization, many times the current thread is still active, but logically the current function does not return, and the thread will process other messages. In other words, synchronization and blocking are actually descriptions of the current thread state from different perspectives under the message notification mechanism.

5.1 Synchronization Blocking Mode

This is the least efficient way. Take the chestnut above, for example, where little J stands in line without distractions and does nothing else.

Here, synchronization and blocking are represented by:

  • Sync: Little J waits in line for his business;
  • Jam: Little J does no other tasks while waiting for the line to reach him.

5.2 Asynchronous Blocking

If little J gets the number when he is waiting for business in the bank, he will wait for the message to be triggered (notification) in an asynchronous way, waiting for the teller to call his number instead of staring at whether he is in line or not. But in the meantime, he can’t leave the bank to do anything else, so he’s obviously stuck waiting for the call.

Here, asynchrony and blocking are represented by:

  • Asynchronous: line to small J, the teller will call his number;
  • Block: You cannot do anything else while waiting for a call.

5.3 Synchronization in non-blocking mode

It’s actually inefficient. Little J can make phone calls while he’s waiting in line, but make phone calls while you see how long it takes to get to him. If you think of making a phone call and observing the queue as two actions in a program, the program needs to switch back and forth between these two different actions.

Here, synchronization and non-blocking are represented by:

  • 1. To wait in line for his turn to do business;
  • Non-blocking: You can make phone calls while you’re in line, but keep checking to see how long it takes to get to him.

5.4 Asynchronous non-blocking mode

This is a more efficient model. Small J can make a call after taking the number, as long as waiting for the teller to call the number can be, call here is waiting for the thing, and notice small J for business is the matter of the teller.

Here, asynchronous and non-blocking are represented by:

  • Asynchronous: the teller called little J to handle business;
  • Non-blocking: in the process of waiting for the number to be called, little J can make a phone call as long as he receives the notice of the number to be called by the teller, and there is no need to pay attention to the progress of the team.

That is, synchronous and asynchronous focus only on the mechanism of how a message is notified, while blocking and non-blocking focus on whether something else can be done while waiting for a message to be notified. In synchronous cases, it is up to the handler to wait for the message to be triggered, while in asynchronous cases, the triggering mechanism informs the handler to process the business.

Five IO models for Linux

After learning about IO operations in Linux and the concepts of synchronous and asynchronous, blocking and non-blocking, let’s take a look at the five IO models implemented in Linux based on synchronous, asynchronous, blocking and non-blocking. For example, recV is a system call that receives a message from a socket. Since it is a system call, it switches from user space to kernel space and then back again. By default, RECV will wait until network data arrives and is copied to user space or an error occurs.

6.1 Synchronous blocking I/O model

The process blocks all the time between the system call recV and copying data from the kernel to user space and back. Just like, little J wants to go to the counter to handle business, if the counter business is busy, he also has to queue, until he finished the line of business, to do other things. Obviously, the IO model is synchronous and blocked.

6.2 Synchronizing the NON-blocking I/O Model

In this case, recV returns whether or not it has received data, and if there is no data, it calls recV again after a while, and so on. Like small J to the counter for business, found the teller rest, he left, after a while and come to see the business did not, until finally encountered the teller for business, this just for business. And little J. gets to do his own thing while he’s away in the middle. However, this model is non-blocking only when checking for no data, and it still waits for data to be copied to user space when it arrives, so it still synchronizes IO.

6.3 I/O Multiplexing Model

In the IO reuse model, recV is preceded by a select or poll call. Both system calls can tell the user process when the kernel is ready (the network data has already arrived in the kernel) that it is ready and that recV must have data when it is called. So in this model, the process blocks on select or poll and not on RECV. Little J came to the bank to do business. The manager told him that all counters were occupied now. He would tell him when there was a vacancy. So little J waited and waited (select or poll call). After a while, the lobby manager told him that there was a counter available for business, but you could find the specific counter by yourself, so little J had to look for the counter one by one.

6.4 Signal-driven IO model

Here the signal function is registered by calling sigAction, and the system interrupts the current program to execute the signal function (in this case recV) when the kernel data is ready. In other words, little J asked the lobby manager to inform him when there was a vacancy at the counter (registration signal function). After a while, the lobby manager informed him that because he was a VIPPP member of the bank, a special counter was opened for him to handle business. So little J went to the special counter to handle business. However, even if the waiting process is non-blocking, the transaction process is still synchronous.

6.5 Asynchronous I/O Model

A call to aio_read causes the kernel to prepare the data, copy it to user process space, and execute the specified function. For example, little J instructed the lobby manager to inform him to accept the business after it had been handled. In this process, little J could do his own thing. This is true asynchronous IO.

As you can see, the first four models are all synchronous IO because copying kernel data into user space is blocked. The last type of asynchronous I/O involves handing over the IO operation to the operating system. The current process does not care about the implementation of the SPECIFIC I/O, and then notifies the current process directly to process the RESULT of the I/O through a callback function or semaphore.

BIO, NIO, AIO

JavaIO provides the implementation of three modes: BIO (synchronous blocking I/O), NIO (synchronous non-blocking I/O), and AIO (asynchronous non-blocking I/O). While the differences between the four patterns have been described in some detail above, I’ll explain the differences between the three JavaIO types.

  • BIO: sync and block, implemented in the server as one thread at a time. That is, when a client has a connection request, the server needs to start a thread to process it. If the connection does not do anything, it incurs unnecessary thread overhead, which can also be alleviated by thread pooling. BIO is generally suitable for architectures with a small and fixed number of connections. It requires a lot of server resources, and the concurrency is limited to the application. It was the only choice before JDK1.4, but the application is intuitive and easy to understand.
  • NIO: Synchronization is not blocking. The pattern implemented in the server is one request at a time, that is, all connection requests sent by the client are registered with the multiplexer. The multiplexer polls for connection IO requests and starts a thread to process them. NIO is generally suitable for architectures with a large number of connections and relatively short (light operation) connections. Concurrency is limited to applications and programming is complex. It is supported starting with JDK1.4.
  • AIO: Asynchronism is not blocking. The mode realized in the server is one effective request to one thread. That is to say, the IO request of the client is completed by the operating system first, and then the server application is notified to start the thread for processing. AIO generally applies to architectures with a large number of connections and long connections (heavy operations). It fully invokes the operating system to participate in concurrent operations, and the programming is complicated. AIO is supported from JDK1.7.

conclusion

This article starts from the operating system for file reading and writing, and introduces the IO modes of synchronous, asynchronous, blocking, non-blocking and their combination. It also introduces five IO models in Linux operating system, and goes back to JavaIO to look at the differences between BIO, NIO and AIO.

If this article is helpful to you, please give a like, it will be my biggest motivation ~

Recently, I have sorted out Java architecture documents and study notes files, architecture videos and HD architecture advanced learning maps to share with everyone for free (including Dubbo, Redis, Netty, ZooKeeper, Spring Cloud, distributed, high concurrency and other architecture technical information). I hope it can help you review before the interview and find a good job, but also save you time to study in the online search materials, you can also pay attention to me and there will be more dry goods to share.

Add the small assistant VX: Xuanwo008 to get data!