Java improvement class (V) in-depth understanding of bio, NiO and AIO

Introduction: in this article, you will get: the performance difference between same / asynchronous + blocking / non blocking; Differences among bio, NiO and AIO; Understand and implement multiplexing when NiO operates socket; At the same time, master the operation skills at the bottom and core of Io.

With the above questions, let's enter the world of Io.

Before we begin, let's think about a question: what is the full name of what we often call "IO"?

Many people may be as confused as I am when they see this problem. The full name of IO is actually the abbreviation of input / output.

1、 IO introduction

What we usually call bio is relative to NiO. Bio is the IO operation module launched at the beginning of Java. Bio is the abbreviation of blockingio. As the name suggests, it means blocking io.

1.1 differences among bio, NiO and AIO

1.2 comprehensive understanding of IO

Traditional IO can be roughly divided into four types:

java. Net, people often classify it as synchronous blocking IO, because network communication is also IO behavior.

java. There are many classes and interfaces under IO, but they are generally subsets of InputStream, OutputStream, writer and reader. Mastering the use of these four classes and files is the key to making good use of Io.

1.3 IO usage

Next, let's look at the inheritance diagram and usage examples of InputStream, OutputStream, writer and reader.

1.3. 1 InputStream usage

Inheritance diagram and class method, as shown in the following figure:

InputStream usage example:

InputStream inputStream = new FileInputStream("D:\\log.txt");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
String str = new String(bytes,"utf-8");
System.out.println(str);
inputStream.close();

1.3. 2 OutputStream usage

Inheritance diagram and class method, as shown in the following figure:

Example of OutputStream usage:

OutputStream outputStream = new FileOutputStream("D:\\log.txt",true); // 参数二,表示是否追加,true=追加
outputStream.write("你好,老王".getBytes("utf-8"));
outputStream.close();

1.3. 3 writer usage

The writer inheritance diagram and class methods are as follows:

Writer usage example:

Writer writer = new FileWriter("D:\\log.txt",true); // 参数二,是否追加文件,true=追加
writer.append("老王,你好");
writer.close();

1.3. 4 reader usage

The reader inheritance diagram and class methods are as follows:

Reader usage example:

Reader reader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(reader);
StringBuffer bf = new StringBuffer();
String str;
while ((str = bufferedReader.readLine()) != null) {
    bf.append(str + "\n");
}
bufferedReader.close();
reader.close();
System.out.println(bf.toString());

2、 Synchronous, asynchronous, blocking, non blocking

The above describes many concepts about synchronization, asynchrony, blocking and non blocking. Next, let's talk about their meanings and the performance analysis formed after the combination.

2.1 synchronous and asynchronous

Synchronization means that when the completion of a task depends on another task, the dependent task can be completed only after the dependent task is completed. This is a reliable task sequence. Either success or failure, the status of the two tasks can be consistent. Asynchrony does not need to wait for the dependent task to complete. It just notifies the dependent task of what work to complete, and the dependent task will be executed immediately. As long as you complete the whole task, it will be completed. As for whether the dependent task is really completed in the end, the task that depends on it cannot be determined, so it is an unreliable task sequence. We can use calling and texting as a good metaphor for synchronous and asynchronous operations.

2.2 blocking and non blocking

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

2.3 same / different, resistance / non blocking combination

There are four types of same / different, resistance / non blocking combinations, as shown in the table below:

3、 Elegant file reading and writing

The reading of files before Java 7 is as follows:

// 添加文件
FileWriter fileWriter = new FileWriter(filePath,true);
fileWriter.write(Content);
fileWriter.close();

// 读取文件
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
StringBuffer bf = new StringBuffer();
String str;
while ((str = bufferedReader.readLine()) != null) {
    bf.append(str + "\n");
}
bufferedReader.close();
fileReader.close();
System.out.println(bf.toString());

Java 7 introduces the of files (under Java. NiO package), which greatly simplifies the reading and writing of files, as follows:

// 写入文件(追加方式:StandardOpenOption.APPEND)
Files.write(Paths.get(filePath),Content.getBytes(StandardCharsets.UTF_8),StandardOpenOption.APPEND);

// 读取文件
byte[] data = Files.readAllBytes(Paths.get(filePath));
System.out.println(new String(data,StandardCharsets.UTF_8));

Reading and writing files is done in one line of code. Yes, this is the most elegant file operation.

There are many useful methods under files, such as creating multi-layer folders. The writing method is also simple:

// 创建多(单)层目录(如果不存在创建,存在不会报错)
new File("D://a//b").mkdirs();

4、 Multiplexing of socket and NiO

This section shows you how to implement NiO multiplexing and socket in AIO while implementing the most basic socket.

4.1 traditional socket implementation

Next, we will implement a simple socket. The server only sends information to the client, and then the client prints it. The code is as follows:

int port = 4343; //端口号
// Socket 服务器端(简单的发送信息)
Thread sThread = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                // 等待连接
                Socket socket = serverSocket.accept();
                Thread sHandlerThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) {
                            printWriter.println("hello world!");
                            printWriter.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                sHandlerThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
sThread.start();

// Socket 客户端(接收信息并打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(),port)) {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
    bufferedReader.lines().forEach(s -> System.out.println("客户端:" + s));
} catch (UnkNownHostException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

In Java, the implementation of threads is relatively heavy, so the startup or destruction of threads consumes the resources of the server. Even if the thread pool is used, using the above traditional socket method, when the number of connections increases greatly, it will also bring performance bottlenecks. The reason is that the online text switching overhead of the line will be obvious at high concurrency, Moreover, the above operation mode is synchronous blocking programming, and the performance problem will be particularly obvious in high concurrency.

The above process is as follows:

4.2 NiO multiplexing

In view of the above high concurrency problems, the multiplexing function of NiO is of great significance.

NiO uses the single thread polling event mechanism to efficiently locate the ready channels to decide what to do. Only the select stage is blocked, which can effectively avoid the problems caused by frequent thread switching when a large number of clients are connected, and the expansion ability of the application has been greatly improved.

// NIO 多路复用
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4,4,60L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
            serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(),port));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
            while (true) {
                selector.select(); // 阻塞等待就绪的Channel
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    try (SocketChannel channel = ((ServerSocketChannel) key.channel()).accept()) {
                        channel.write(Charset.defaultCharset().encode("你好,世界"));
                    }
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});

// Socket 客户端(接收信息并打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(),port)) {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
    bufferedReader.lines().forEach(s -> System.out.println("NIO 客户端:" + s));
} catch (IOException e) {
    e.printStackTrace();
}

The following figure can effectively illustrate the NiO reuse process:

In this way, NiO multiplexing greatly improves the ability of server-side response to high concurrency.

4.3 socket implementation of AIO version

The socket implemented by AIO is provided in Java 1.7. The code is as follows:

// AIO线程复用版
Thread sThread = new Thread(new Runnable() {
    @Override
    public void run() {
        AsynchronousChannelGroup group = null;
        try {
            group = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4));
            AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(InetAddress.getLocalHost(),port));
            server.accept(null,new CompletionHandler<AsynchronousSocketChannel,AsynchronousServerSocketChannel>() {
                @Override
                public void completed(AsynchronousSocketChannel result,AsynchronousServerSocketChannel attachment) {
                    server.accept(null,this); // 接收下一个请求
                    try {
                        Future<Integer> f = result.write(Charset.defaultCharset().encode("你好,世界"));
                        f.get();
                        System.out.println("服务端发送时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                        result.close();
                    } catch (InterruptedException | ExecutionException | IOException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void Failed(Throwable exc,AsynchronousServerSocketChannel attachment) {
                }
            });
            group.awaitTermination(Long.MAX_VALUE,TimeUnit.SECONDS);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
});
sThread.start();

// Socket 客户端
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
Future<Void> future = client.connect(new InetSocketAddress(InetAddress.getLocalHost(),port));
future.get();
ByteBuffer buffer = ByteBuffer.allocate(100);
client.read(buffer,null,new CompletionHandler<Integer,Void>() {
    @Override
    public void completed(Integer result,Void attachment) {
        System.out.println("客户端打印:" + new String(buffer.array()));
    }

    @Override
    public void Failed(Throwable exc,Void attachment) {
        exc.printStackTrace();
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
Thread.sleep(10 * 1000);

5、 Summary

The above is basically IO from 1.0 to the current version (the version of this article) the core of JDK 8 uses operations. It can be seen that IO, as a common basic function, has undergone great changes, and it is becoming simpler and simpler to use. IO operations are easy to understand. Input and output are one by one. Mastering input and output will also master io. Socket, as an integrated function of network interaction, obviously n IO multiplexing brings more vitality and choices to the socket. Users can choose the corresponding code strategy according to their actual scenarios.

Of course, at the end of this article, I also give you the example code of this article: https://github.com/vipstone/java-core-example

6、 Reference documents

http://t.cn/EwUJvWA

https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>