Mastering the basic concepts of BIO,NIO, and AIO, as well as faQs, should be an integral part of your interview preparation process, as well as a foundation for learning Netty.
BIO, NIO, AIO summary
BIO, NIO and AIO in Java are the encapsulation of various IO models of the operating system in Java language. Programmers use these apis without the need to care about operating system knowledge or write different code for different operating systems. Just use the Java API. Before we get to BIO,NIO, and AIO, let’s review a few concepts: synchronous versus asynchronous, blocking versus non-blocking. Synchronous and asynchronous
- Synchronization: Synchronization is when a call is made and does not return until the caller has processed the request.
- Asynchrony: Asynchrony means that after making a call, we immediately get the response from the called to indicate that the request has been received, but the called does not return the result. At this time, we can process other requests. The called usually relies on events, callbacks and other mechanisms to notify the caller of the return result. The biggest difference between synchronous and asynchronous is that in asynchronous case, the caller does not need to wait for the processing result, and the called will notify the caller of the return result through mechanisms such as callback. Blocking and non-blocking
- Block: A block is when a request is made and the caller waits for the result of the request to return, meaning that the current thread is suspended and cannot continue until the condition is ready.
- Non-blocking: Non-blocking is making a request so the caller doesn’t have to wait for the result to come back and can do something else first.
So what do synchronous blocking, synchronous non-blocking, and asynchronous non-blocking mean? Take a simple example in life: your mother asked you to boil water. When you were a child, you were stupid and waited for the water to boil. As you get a little older, you’ll know that every time you boil the water you can do something else, and then you’ll just have to check in every now and then to see if the water is boiling (synchronous non-blocking). Later, you put water in your house and turn on a kettle that makes a sound, so you only need to hear the sound to know the water is boiling, in the meantime you can do whatever you want, you need to pour the water (asynchronous non-blocking).
1. BIO (Blocking I/O)
Synchronous blocking in I/O mode, data reads and writes must be blocked in a thread waiting for them to complete.
1.1 the traditional BIO
BIO communication (one request, one reply) model diagram is as follows (figure source network, origin unknown) :
BIO communication model
while(true)
accept()
Multithreading is necessary if the BIO communication model is to be able to handle multiple client requests at the same time (mainly because the three main functions involved in socket.accept(), socket.read(), and socket.write() all block synchronously). That is, it creates a new thread for each client to process the link after receiving the client connection request, and then sends the reply back to the client via the output stream, and the thread is destroyed. This is the typical one – request – one – reply communication model. We can imagine unnecessary thread overhead if the connection does nothing, but this can be remedied by thread pooling, which also makes thread creation and collection relatively cheap. Using FixedThreadPool can effectively control the maximum number of threads, ensure the control of limited resources of the system, and realize the pseudo asynchronous I/O model of N(number of client requests):M(number of threads processing client requests) (N can be much larger than M). This is covered in more detail in the next section “Pseudo-asynchronous BIO”.
Let’s imagine what happens to this model as the number of concurrent client visits increases.
Threads are a valuable resource in a Java virtual machine, costly to create and destroy, and costly to switch over. Especially in operating systems like Linux, a thread is essentially a process, and creating and destroying threads are heavyweight system functions. If the number of concurrent visits increases, the number of threads expands rapidly. As a result, the stack of threads overflows and new threads fail to be created. As a result, processes break down or die and cannot provide external services.
1.2 Pseudo Asynchronous I/O
In order to solve the synchronous blocking I/O face a link need a thread to handle the problem, then some of the threading model are optimized after the 111 side through a thread pool to deal with multiple client requests access to form the client number M: thread pool threads N largest proportion relations, which M can be far greater than N. Through the thread pool, thread resources can be flexibly allocated and the maximum number of threads can be set to prevent thread exhaustion caused by massive concurrent access.
Pseudo asynchronous IO model diagram (figure source network, original source unknown) :
Using thread pools and task queues, you can implement a framework for I/O communication called pseudo-asynchronous, which is modeled as shown in the figure above. When a new client is connected, the client Socket is encapsulated into a Task (which implements the java.lang.Runnable interface) and sent to the backend thread pool for processing. The JDK thread pool maintains a message queue and N active threads to process the tasks in the message queue. Because the thread pool can set the size of the message queue and the maximum number of threads, its resource usage is manageable, and no matter how many clients access it concurrently, it will not cause resource exhaustion or downtime.
The pseudo-asynchronous I/O communication framework uses a thread pool implementation, thus avoiding the thread resource depletion problem caused by creating a separate thread for each request. However, because its underlying BIO model is still synchronous blocking, it cannot solve the problem fundamentally.
1.3 Code Examples
The BIO communication (one request, one reply) model is demonstrated in the following code. We will create multiple threads on the client side to connect to the server in turn and send it “current time +: Hello world”, and the server will create a thread for each client thread to process. The code sample is from the Flash blog. The original address is: client
* * @author * @date 2018年10月14日 * @description: Client */ public class IOClient {public static void main(String[] args) New Thread(() -> {try {Socket Socket = new Socket()"127.0.0.1", 3333);
while (true) {
try {
socket.getOutputStream().write((new Date() + ": hello world").getBytes()); Thread.sleep(2000); } catch (Exception e) { } } } catch (IOException e) { } }).start(); }}Copy the code
The service side
* @author Flash * @date October 14, 2018 * @Description: Server */ public class IOServer {public static void main(String[] args) throws IOException {// The TODO server processes client connection requests ServerSocket serverSocket = new ServerSocket(3333); New Thread(() -> {new Thread(()) -> {while (true) {try {// block method to get new connection Socket Socket = serversocket.accept (); New Thread(() -> {try {int len; byte[] data = new byte[1024]; InputStream inputStream = socket.getInputStream(); // Read data in byte stream modewhile ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
} catch (IOException e) {
}
}).start();
} catch (IOException e) {
}
}
}).start();
}
}
Copy the code
1.4 summarize
In the case that the number of active connections is not particularly high (less than 1000 for a single machine), this model is relatively good, allowing each connection to focus on its OWN I/O and simple programming model, without too much consideration of system overload, current limiting and other problems. The thread pool itself is a natural funnel to buffer connections or requests that the system can’t handle. 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.
2. NIO (New I/O)
2.1 introduction of NIO
NIO is a synchronous non-blocking I/O model. The NIO framework 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. NIO provides two different Socket channel implementations, SocketChannel and ServerSocketChannel, that correspond to Socket and ServerSocket in the traditional BIO model. Both channels support both blocking and non-blocking modes. Blocking mode, like traditional support, is relatively simple, but has poor performance and reliability. Non-blocking mode is the opposite. For low-load, low-concurrency applications, synchronous blocking I/O can be used for faster development and better maintenance; For high-load, high-concurrency (network) applications, NIO’s non-blocking mode should be used for development.
2.2 NIO Features/Differences between NIO and IO
NIO streams do not block IO while IO streams block IO. It can then be analyzed in terms of some of the improvements that NIO’s three core components/features bring to NIO. And if you do that, I think you’ll have a little bit more insight into NIO, and you’ll be able to easily answer that question when the interviewer asks you.
1)Non-blocking IO
IO streams are blocked, NIO streams are not blocked.
Java NIO allows us to do non-blocking IO operations. For example, a single thread can read data from a channel to buffer and continue doing other things at the same time. When data is read into buffer, the thread can continue processing data. The same is true for writing data. In addition, the same is true for non-blocking writes. A thread requests to write some data to a channel, but without waiting for it to write completely, the thread can do something else in the meantime.
The various streams of Java IO are blocked. This means that when a thread calls read() or write(), the thread blocks until some data is read, or data is written entirely. The thread can’t do anything else in the meantime
2)Buffer
IO is Stream oriented, while NIO is Buffer oriented.
A Buffer is an object that contains some data to be written or read. The addition of Buffer objects to the NIO class library represents an important difference between the new library and the original I/O. In stream-oriented I/O · Data can be written directly or read directly into a Stream object. There are Buffer extension classes in Stream, but they wrap the Stream and read from the Stream into the Buffer, whereas NIO reads directly into Buffer.
In NIO, all data is handled with buffers. When reading data, it reads directly into the buffer; When writing data, it is written to a buffer. Any time you access data in NIO, you are operating through the buffer.
The most commonly used buffer is the ByteBuffer, a ByteBuffer that provides a set of functions for manipulating byte arrays. There are other buffers besides ByteBuffers, and in fact, there should be a buffer for every Java primitive (except Boolean) type.
3)Channel
NIO reads and writes through channels.
A channel is bidirectional and can be read or written, while a stream reads and writes in one direction. Channels can only interact with buffers, whether they are read or written. Because of buffers, channels can be read and written asynchronously.
4)Selectors
NIO has selectors, while IO does not.
Selectors are used to process multiple channels using a single thread. Therefore, it requires fewer threads to process these channels. Switching between threads can be expensive for an operating system. Therefore, selectors are useful in order to improve system efficiency.
2.3 NIO Data read and write modes
In general, all IO in NIO starts with a Channel.
- Read data from the channel: Create a buffer and then ask the channel to read data.
- Write data from channel: Create a buffer, populate the data, and ask the channel to write data.
Data read and write operation diagram:
2.4 NIO core Components
NIO consists of the following core components:
- Channel (Channel)
- Buffer
- Selector
The entire NIO architecture contains far more classes than these three, which can only be described as the “core API” of the NIO architecture. We have explained these three concepts in basic terms above, so there is no more explanation here.
2.5 Code Examples
The code sample is from the Flash blog.
www.jianshu.com/p/a4e038359…
The client ioclient.java code remains the same, and we modify the server to use NIO. The following code more and logic is more complex, we have a good look.
* * @author Flash * @date February 21, 2019 * @description: Public class NIOServer {public static void main(String[] args) throws IOException {// 1. The serverSelector is responsible for polling for new connections, and instead of creating a new thread when the server detects a new connection, it binds the new connection directly to the clientSelector, thus eliminating the 1w connections in the IO modelwhileLoop in dead Selector serverSelector = Selector. Open (); ClientSelector = Selector. Open (); ListenerChannel = ServerSocketChannel.open(); listenerChannel = ServerSocketChannel.open(); listenerChannel.socket().bind(new InetSocketAddress(3333)); listenerChannel.configureBlocking(false);
listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
while (true) {// Monitor for new connections, where 1 refers to the blocking time of 1msif (serverSelector.select(1) > 0) {
Set<SelectionKey> set = serverSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = set.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isacceptable ()) {try {// (1) // No thread needs to be created for each new connection, ClientSelector SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); clientChannel.configureBlocking(false);
clientChannel.register(clientSelector, SelectionKey.OP_READ);
}
finally {
keyIterator.remove();
}
}
}
}
}
}
catch (IOException ignored) {
}
}
).start();
new Thread(() -> {
try {
while (true) {/ / (2) batch polling to see if there is any connection with data to be read, 1 refers to the time here is 1 msif (clientSelector.select(1) > 0) {
Set<SelectionKey> set = clientSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = set.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isReadable()) { try { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // (3) Clientchannel.read (byteBuffer); byteBuffer.flip(); System.out.println( Charset.defaultCharset().newDecoder().decode(byteBuffer).toString()); } finally { keyIterator.remove(); key.interestOps(SelectionKey.OP_READ); } } } } } } catch (IOException ignored) { } } ).start(); }}Copy the code
Why doesn’t anyone want to develop in JDK native NIO? As you can see from the above code, it is really difficult to use! In addition to complicated programming and difficult programming model, it also has the following shortcomings:
- The NIO underlayer of the JDK is implemented by ePoll, whose much-maligned empty polling bug causes CPU spikes of up to 100%
- After a large project, the self-implemented NIO is prone to various bugs and the maintenance cost is high. I can’t guarantee that there are no bugs in the above pile of code
The advent of Netty has greatly improved some of the annoying problems with JDK native NIO.
3. AIO (Asynchronous I/O)
AIO is NIO 2. NIO 2, an improved version of NIO introduced in Java 7, is an asynchronous, non-blocking 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.
AIO is short for asynchronous IO. Although NIO provides a non-blocking method for network operations, NIO’s I/O behavior is synchronous. For NIO, our business thread is notified when the IO operation is ready, and then the thread performs the IO operation itself, which is synchronous.