Evolution of Java I/O models and java Evolution

Source: Internet
Author: User
Tags java se

Evolution of Java I/O models and java Evolution

What is synchronization? What is asynchronous? What is the difference between blocking and non-blocking? This article first introduces five common I/O models from the Unix I/O model. Then we will introduce the evolution process of the Java I/O model and use examples to explain how to select an appropriate Java I/O model to improve the concurrency and availability of the system.

Because Java I/O depends on the implementation of the operating system, first understanding the Unix I/O model helps to understand Java I/O.

Related Concepts: synchronous and asynchronous

Describes the interaction between the user thread and the kernel:

  • SynchronizationAfter the user thread initiates an I/O request, it must wait or wait until the kernel I/O operation is completed;
  • AsynchronousIt means that the user thread continues to execute after initiating an I/O request. After the kernel I/O operation is completed, the user thread is notified or the callback function registered by the user thread is called.
Blocking and non-blocking

Describes how the user thread calls the kernel I/O operation:

  • BlockingIt means that I/O operations need to be completely completed before they are returned to the user space;
  • Non-blockingThis means that an I/O operation is returned to the user immediately after it is called, and you do not need to wait until the I/O operation is complete.

An I/O operation is divided into two steps: initiating an I/O request and the actual I/O operation. The difference between blocking I/O and non-blocking I/O is that the first step is to determine whether an I/O request will be blocked. If blocking is completed, it is the traditional blocking I/O, if it is not blocked, it is non-blocking I/O. The difference between synchronous I/O and asynchronous I/O lies in whether the second step is blocked. If the actual I/O read/write blocking request process, it is synchronous I/O.

Unix I/O model

There are five I/O models in Unix:

Blocking I/O

If the request cannot be completed immediately, the request is blocked.

  • Phase 1: Wait for the data to be ready. Network I/O is waiting for the arrival of remote data; disk I/O is waiting for the disk data to be read from the disk to the kernel memory.
  • Phase 2: Data Copying. For system security, the user-State program does not have the permission to directly read the kernel-state memory. Therefore, the kernel is responsible for copying the data in the kernel-state memory to the user-state memory.

Non-blocking I/O
  • When the socket is set to NONBLOCK (non-blocking), it tells the kernel that when the requested I/O operation cannot be completed, do not sleep the process, but return an error code (EWOULDBLOCK ), in this way, the request will not be blocked.
  • The I/O operation function will continuously test whether the data is ready. If not, continue the test until the data is ready. During the whole I/O request process, although the user thread can return immediately after each I/O request is initiated, in order to wait until the data is processed, the user needs to continuously poll and repeat the request, consumes a lot of CPU resources
  • The data is ready and copied from the kernel to the user space.

This model is rarely used directly, but non-blocking I/O is used in other I/O models. This method is of little significance to a single I/O request, but paves the way for I/O multiplexing.

I/O multiplexing (asynchronous blocking I/O)

I/O multiplexing will use select or poll functions. These two functions will also cause process blocking, but different from blocking I/O, these two functions can block Multiple I/O operations at the same time. In addition, I/O functions of multiple read operations and write operations can be detected at the same time. I/O operation functions can be called only when data is readable or writable.

From the process point of view, there is no big difference between using the select function for I/O requests and the synchronous blocking model, or even adding monitoring sockets and calling the select function for extra operations, which is less efficient. However, the biggest advantage after using select is that you can process multiple socket I/O Requests simultaneously in one thread. You can register multiple sockets and call select to read the activated socket to process multiple I/O Requests simultaneously in the same thread. In the synchronous blocking model, you must use multiple threads to achieve this goal.

The I/O multiplexing model uses the Reactor design mode to implement this mechanism.

The select/poll method is called. A user-state thread is responsible for polling multiple sockets until the data in phase 1 is ready, and then notifies the actual user thread to copy the data in Phase 2 of execution. A dedicated user-state thread is used to execute non-blocking I/O round-robin, simulating Phase 1 Asynchronization

Signal-driven I/O (SIGIO)

First, we allow the socket to drive I/O and install a signal processing function. The process continues to run without blocking. When the data is ready, the process receives a SIGIO signal and can call the I/O operation function in the signal processing function to process the data.

Asynchronous I/O

Call the aio_read function to tell the kernel description, buffer pointer, buffer size, file offset, and notification method, and then return immediately. When the kernel copies the data to the buffer, it notifies the application.

The asynchronous I/O model uses the Proactor design mode to implement this mechanism.

Inform the kernel that the application is notified to read data when the entire process (including phase 1 and phase 2) is completed.

Comparison of several I/O models

The difference between the first four models is that phase 1 is different, and phase 2 is basically the same. Data is copied from the kernel to the caller's buffer zone. The two phases of asynchronous I/O are different from the first four models.

The synchronous I/O operation causes the request process to be blocked until the I/O operation is completed. Asynchronous I/O operations do not cause request process blocking.

Common Java I/O models

After learning about the unix I/O model, the Java I/O model is similar.

Blocking I/O mode

In the Socket section above, EchoServer is a simple example of blocking I/O. After the server is started, wait for the client to connect. After the client connects to the server, the server blocks reading and writing data streams.

EchoServer code:

public class EchoServer {    public static int DEFAULT_PORT = 7;    public static void main(String[] args) throws IOException {        int port;        try {            port = Integer.parseInt(args[0]);        } catch (RuntimeException ex) {            port = DEFAULT_PORT;        }        try (            ServerSocket serverSocket =                new ServerSocket(port);            Socket clientSocket = serverSocket.accept();                 PrintWriter out =                new PrintWriter(clientSocket.getOutputStream(), true);                               BufferedReader in = new BufferedReader(                new InputStreamReader(clientSocket.getInputStream()));        ) {            String inputLine;            while ((inputLine = in.readLine()) != null) {                out.println(inputLine);            }        } catch (IOException e) {            System.out.println("Exception caught when trying to listen on port "                + port + " or listening for a connection");            System.out.println(e.getMessage());        }    }}
Improved to the "blocking I/O + multithreading" Mode

Multithreading is used to support multiple clients to access the server.

Main thread MultiThreadEchoServer. java

public class MultiThreadEchoServer {    public static int DEFAULT_PORT = 7;    public static void main(String[] args) throws IOException {        int port;        try {            port = Integer.parseInt(args[0]);        } catch (RuntimeException ex) {            port = DEFAULT_PORT;        }        Socket clientSocket = null;        try (ServerSocket serverSocket = new ServerSocket(port);) {            while (true) {                clientSocket = serverSocket.accept();                // MultiThread                new Thread(new EchoServerHandler(clientSocket)).start();            }        } catch (IOException e) {            System.out.println(                    "Exception caught when trying to listen on port " + port + " or listening for a connection");            System.out.println(e.getMessage());        }    }}

Processor class EchoServerHandler. java

public class EchoServerHandler implements Runnable {    private Socket clientSocket;    public EchoServerHandler(Socket clientSocket) {        this.clientSocket = clientSocket;    }    @Override    public void run() {        try (PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);                BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));) {            String inputLine;            while ((inputLine = in.readLine()) != null) {                out.println(inputLine);            }        } catch (IOException e) {            System.out.println(e.getMessage());        }    }}

Problems: Each time a new connection is received, a new thread is created. After processing, the thread is destroyed, which is costly. When a large number of short connections occur, the performance is relatively low.

Improved to the "blocking I/O + thread pool" Mode

The thread pool can be used to optimize the overhead caused by repeated thread creation and destruction in the multi-threaded model above. Each time a new connection is received, an idle thread is taken from the pool for processing. After the processing is completed, the thread is put back in the pool. reusing the thread avoids the overhead caused by frequent thread creation and destruction.

Main thread ThreadPoolEchoServer. java

public class ThreadPoolEchoServer {    public static int DEFAULT_PORT = 7;    public static void main(String[] args) throws IOException {        int port;        try {            port = Integer.parseInt(args[0]);        } catch (RuntimeException ex) {            port = DEFAULT_PORT;        }        ExecutorService threadPool = Executors.newFixedThreadPool(5);        Socket clientSocket = null;        try (ServerSocket serverSocket = new ServerSocket(port);) {            while (true) {                clientSocket = serverSocket.accept();                // Thread Pool                threadPool.submit(new Thread(new EchoServerHandler(clientSocket)));            }        } catch (IOException e) {            System.out.println(                    "Exception caught when trying to listen on port " + port + " or listening for a connection");            System.out.println(e.getMessage());        }    }}

Problems: In scenarios with a large number of transient connections, the performance will be improved, because you do not need to create and destroy threads every time, but reuse the threads in the connection pool. However, in a large number of persistent connections, threads are not frequently created and destroyed because they are used by connections for a long time. Therefore, there is no advantage.

Improved to "non-blocking I/O" Mode

Although the network model of "blocking I/O + thread pool" is better than "blocking I/O + multithreading, however, both models share a common problem: both read and write operations are blocked synchronously. In the face of high concurrency (sustained massive connections and simultaneous requests) scenarios, A large number of threads are required to maintain the connection. The CPU is frequently switched among a large number of threads, causing high performance loss. Once the single-host connection exceeds 10 thousand or even tens of thousands, the server performance will drop sharply.

NIO's Selector solves this problem well. It uses the main thread (a thread or a thread with the number of CPUs) to keep the established connection and manage and read the data connected to the client, the read data is handed over to the subsequent thread pool for processing. After the thread pool finishes processing the business logic, it sends the result to the main thread to send a response to the client. A small number of threads can process a large number of connection requests.

Java NIO consists of the following core components:

  • Channel
  • Buffer
  • Selector

To use Selector, you must register a Channel with Selector and call its select () method. This method will be blocked until a registered channel has an event ready. Once this method is returned, the thread can process these events, such as new connections and data reception.

Main thread NonBlokingEchoServer. java

public class NonBlokingEchoServer {    public static int DEFAULT_PORT = 7;    public static void main(String[] args) throws IOException {        int port;        try {            port = Integer.parseInt(args[0]);        } catch (RuntimeException ex) {            port = DEFAULT_PORT;        }        System.out.println("Listening for connections on port " + port);        ServerSocketChannel serverChannel;        Selector selector;        try {            serverChannel = ServerSocketChannel.open();            InetSocketAddress address = new InetSocketAddress(port);            serverChannel.bind(address);            serverChannel.configureBlocking(false);            selector = Selector.open();            serverChannel.register(selector, SelectionKey.OP_ACCEPT);        } catch (IOException ex) {            ex.printStackTrace();            return;        }        while (true) {            try {                selector.select();            } catch (IOException ex) {                ex.printStackTrace();                break;            }            Set<SelectionKey> readyKeys = selector.selectedKeys();            Iterator<SelectionKey> iterator = readyKeys.iterator();            while (iterator.hasNext()) {                SelectionKey key = iterator.next();                iterator.remove();                try {                    if (key.isAcceptable()) {                        ServerSocketChannel server = (ServerSocketChannel) key.channel();                        SocketChannel client = server.accept();                        System.out.println("Accepted connection from " + client);                        client.configureBlocking(false);                        SelectionKey clientKey = client.register(selector,                                SelectionKey.OP_WRITE | SelectionKey.OP_READ);                        ByteBuffer buffer = ByteBuffer.allocate(100);                        clientKey.attach(buffer);                    }                    if (key.isReadable()) {                        SocketChannel client = (SocketChannel) key.channel();                        ByteBuffer output = (ByteBuffer) key.attachment();                        client.read(output);                    }                    if (key.isWritable()) {                        SocketChannel client = (SocketChannel) key.channel();                        ByteBuffer output = (ByteBuffer) key.attachment();                        output.flip();                        client.write(output);                        output.compact();                    }                } catch (IOException ex) {                    key.cancel();                    try {                        key.channel().close();                    } catch (IOException cex) {                    }                }            }        }    }}
Improved to asynchronous I/O mode

After Java SE 7, asynchronous I/O (NIO.2) support is introduced, which provides a powerful tool for building high-performance network applications.

Main thread AsyncEchoServer. java

public class AsyncEchoServer {    public static int DEFAULT_PORT = 7;    public static void main(String[] args) throws IOException {        int port;        try {            port = Integer.parseInt(args[0]);        } catch (RuntimeException ex) {            port = DEFAULT_PORT;        }        ExecutorService taskExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory());        // create asynchronous server socket channel bound to the default group        try (AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open()) {            if (asynchronousServerSocketChannel.isOpen()) {                // set some options                asynchronousServerSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 4 * 1024);                asynchronousServerSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);                // bind the server socket channel to local address                asynchronousServerSocketChannel.bind(new InetSocketAddress(port));                // display a waiting message while ... waiting clients                System.out.println("Waiting for connections ...");                while (true) {                    Future<AsynchronousSocketChannel> asynchronousSocketChannelFuture = asynchronousServerSocketChannel                            .accept();                    try {                        final AsynchronousSocketChannel asynchronousSocketChannel = asynchronousSocketChannelFuture                                .get();                        Callable<String> worker = new Callable<String>() {                            @Override                            public String call() throws Exception {                                String host = asynchronousSocketChannel.getRemoteAddress().toString();                                System.out.println("Incoming connection from: " + host);                                final ByteBuffer buffer = ByteBuffer.allocateDirect(1024);                                // transmitting data                                while (asynchronousSocketChannel.read(buffer).get() != -1) {                                    buffer.flip();                                    asynchronousSocketChannel.write(buffer).get();                                    if (buffer.hasRemaining()) {                                        buffer.compact();                                    } else {                                        buffer.clear();                                    }                                }                                asynchronousSocketChannel.close();                                System.out.println(host + " was successfully served!");                                return host;                            }                        };                        taskExecutor.submit(worker);                    } catch (InterruptedException | ExecutionException ex) {                        System.err.println(ex);                        System.err.println("\n Server is shutting down ...");                        // this will make the executor accept no new threads                        // and finish all existing threads in the queue                        taskExecutor.shutdown();                        // wait until all threads are finished                        while (!taskExecutor.isTerminated()) {                        }                        break;                    }                }            } else {                System.out.println("The asynchronous server-socket channel cannot be opened!");            }        } catch (IOException ex) {            System.err.println(ex);        }    }}
Source code

The source code of the example in this chapter, can be in the https://github.com/waylau/essential-javacom.waylau.essentialjava.net.echoPackage.

 

Q: customized IT education platform, one-to-one service, Q & A. Official Website for developing programming social headlines: www.wenaaa.com

QQ Group 290551701 has gathered many Internet elites, Technical Directors, architects, and project managers! Open-source technology research, welcome to the industry, Daniel and beginners interested in IT industry personnel!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.