01-Introducción al modelo IO

Modelo IO

El modelo IO significa qué tipo de canal se utiliza para enviar y recibir datos. Java admite 3 modos IO de programación de red: BIO, NIO, AIO

BIO (Bloqueo de E / S)

Modelo de bloqueo síncrono, una conexión de cliente corresponde a un hilo de procesamiento

Ejemplo de código BIO:

Código del servidor

package com.tuling.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接。。");
            //阻塞方法
            Socket socket = serverSocket.accept();
            System.out.println("有客户端连接了。。");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        handler(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            //handler(socket);

        }
    }

    private static void handler(Socket socket) throws IOException {
        System.out.println("thread id = " + Thread.currentThread().getId());
        byte[] bytes = new byte[1024];

        System.out.println("准备read。。");
        //接收客户端的数据,阻塞方法,没有数据可读时就阻塞
        int read = socket.getInputStream().read(bytes);
        System.out.println("read完毕。。");
        if (read != -1) {
            System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
            System.out.println("thread id = " + Thread.currentThread().getId());

        }
        socket.getOutputStream().write("HelloClient".getBytes());
        socket.getOutputStream().flush();
    }
}

Codigo del cliente 

package com.tuling.bio;

import java.io.IOException;
import java.net.Socket;

public class SocketClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 9000);
        //向服务端发送数据
        socket.getOutputStream().write("HelloServer".getBytes());
        socket.getOutputStream().flush();
        System.out.println("向服务端发送数据结束");
        byte[] bytes = new byte[1024];
        //接收服务端回传的数据
        socket.getInputStream().read(bytes);
        System.out.println("接收到服务端的数据:" + new String(bytes));
        socket.close();
    }
}

Desventajas:

1. La operación de lectura en el código IO es una operación de bloqueo. Si la conexión no realiza operaciones de lectura y escritura de datos, hará que el hilo se bloquee y desperdicie recursos.

2. Si hay demasiados subprocesos, provocará demasiados subprocesos del servidor y demasiada presión, como el problema C10K

Escenarios de aplicación:

El método BIO es adecuado para una arquitectura con un número relativamente pequeño de conexiones y un número fijo de conexiones Este método requiere mayores recursos del servidor, pero el programa es simple y fácil de entender.

NIO (E / S sin bloqueo)

Sin bloqueo sincrónico, el modo de implementación del servidor es que un hilo puede manejar múltiples solicitudes (conexiones), las solicitudes de conexión enviadas por el cliente se registrarán en el selector de multiplexor y el multiplexor sondea hasta que haya una solicitud de IO conectada Procesamiento, JDK1 .4 comenzó a introducirse.

Escenarios de aplicación:

El método NIO es adecuado para arquitecturas con una gran cantidad de conexiones y conexiones relativamente cortas (operación ligera), como servidores de chat, sistemas de barrera, comunicación entre servidores y programación complicada.

Ejemplo de código sin bloqueo NIO:

NioServer 

package com.tuling.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOServer {

    //public static ExecutorService pool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws IOException {
        // 创建一个在本地端口进行监听的服务Socket通道.并设置为非阻塞方式
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //必须配置为非阻塞才能往selector上注册,否则会报错,selector模式本身就是非阻塞模式
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(9000));
        // 创建一个选择器selector
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            System.out.println("等待事件发生。。");
            // 轮询监听channel里的key,select是阻塞的,accept()也是阻塞的
            int select = selector.select();

            System.out.println("有事件发生了。。");
            // 有客户端请求,被轮询监听到
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //删除本次已处理的key,防止下次select重复处理
                it.remove();
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            System.out.println("有客户端连接事件发生了。。");
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //NIO非阻塞体现:此处accept方法是阻塞的,但是这里因为是发生了连接事件,所以这个方法会马上执行完,不会阻塞
            //处理完连接请求不会继续等待客户端的数据发送
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            //通过Selector监听Channel时对读事件感兴趣
            sc.register(key.selector(), SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            System.out.println("有客户端数据可读事件发生了。。");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //NIO非阻塞体现:首先read方法不会阻塞,其次这种事件响应模型,当调用到read方法时肯定是发生了客户端发送数据的事件
            int len = sc.read(buffer);
            if (len != -1) {
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
            sc.write(bufferToWrite);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        } else if (key.isWritable()) {
            SocketChannel sc = (SocketChannel) key.channel();
            System.out.println("write事件");
            // NIO事件触发是水平触发
            // 使用Java的NIO编程的时候,在没有数据可以往外写的时候要取消写事件,
            // 在有数据往外写的时候再注册写事件
            key.interestOps(SelectionKey.OP_READ);
            //sc.close();
        }
    }
}

nioClient 

package com.tuling.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioClient {
    //通道管理器
    private Selector selector;

    /**
     * 启动客户端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NioClient client = new NioClient();
        client.initClient("127.0.0.1", 9000);
        client.connect();
    }

    /**
     * 获得一个Socket通道,并对该通道做一些初始化的工作
     *
     * @param ip   连接的服务器的ip
     * @param port 连接的服务器的端口号
     * @throws IOException
     */
    public void initClient(String ip, int port) throws IOException {
        // 获得一个Socket通道
        SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 获得一个通道管理器
        this.selector = Selector.open();

        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
        //用channel.finishConnect() 才能完成连接
        channel.connect(new InetSocketAddress(ip, port));
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     *
     * @throws IOException
     */
    public void connect() throws IOException {
        // 轮询访问selector
        while (true) {
            selector.select();
            // 获得selector中选中的项的迭代器
            Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                // 删除已选的key,以防重复处理
                it.remove();
                // 连接事件发生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    // 如果正在连接,则完成连接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();
                    }
                    // 设置成非阻塞
                    channel.configureBlocking(false);
                    //在这里可以给服务端发送信息哦
                    ByteBuffer buffer = ByteBuffer.wrap("HelloServer".getBytes());
                    channel.write(buffer);
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);                                            // 获得了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }
            }
        }
    }

    /**
     * 处理读取服务端发来的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        //和服务端的read方法一样
        // 服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = channel.read(buffer);
        if (len != -1) {
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        }
    }
}

 

para resumir:

Si hay demasiadas conexiones, habrá una gran cantidad de recorridos no válidos. Si hay 10,000 conexiones, solo 1,000 de ellas tienen datos de escritura, pero debido a que las otras 9,000 conexiones no están desconectadas, aún tenemos que sondear el recorrido 10,000 cada vez. En segundo lugar, nueve de cada diez recorridos son inválidos, lo que obviamente no es un estado muy satisfactorio.

NIO presenta ejemplos de código de multiplexor:

package com.tuling.nio;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioSelectorServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 创建NIO ServerSocketChannel
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        // 设置ServerSocketChannel为非阻塞
        serverSocket.configureBlocking(false);
        // 打开Selector处理Channel,即创建epoll
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动成功");
        while (true) {
            // 阻塞等待需要处理的事件发生
            selector.select();
            // 获取selector中注册的全部事件的 SelectionKey 实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 遍历SelectionKey对事件进行处理
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 如果是OP_ACCEPT事件,则进行连接获取和事件注册
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    // 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接成功");
                } else if (key.isReadable()) {
                    // 如果是OP_READ事件,则进行读取和打印
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = socketChannel.read(byteBuffer);
                    // 如果有数据,把数据打印出来
                    if (len > 0) {
                        System.out.println("接收到消息:" + new String(byteBuffer.array()));
                    } else if (len == -1) {
                        // 如果客户端断开连接,关闭Socket
                        // System.out.println("客户端断开连接"); socketChannel.close();
                    }
                }
                // 从事件集合里删除本次处理的key,防止下次select重复处理
                iterator.remove();
            }
        }
    }
}

NIO tiene tres componentes principales:

Canal (canal), búfer (búfer), selector (multiplexor)

1. Un canal es similar a una secuencia, cada canal corresponde a un búfer de búfer y la capa inferior del búfer es una matriz.

2. El canal se registrará en el selector y el selector lo entregará a un subproceso inactivo para su procesamiento de acuerdo con la ocurrencia de eventos de lectura y escritura del canal.

3. El búfer y el canal de NIO se pueden leer y escribir

La capa inferior de NIO en la versión JDK1.4 se implementa usando la función del kernel de Linux select () o poll (). Similar al código de NioServer anterior, el selector sondea todos los sockchannels cada vez para ver qué canal tiene eventos de lectura y escritura, y algunos si no, entonces continúe atravesando JDK1.5 comenzó a introducir epoll para optimizar NIO basado en el mecanismo de respuesta a eventos.

Los siguientes métodos en el código de NioSelectorServer son muy importantes. Vamos a entenderlo desde el nivel de las funciones del kernel de Linux y Hotspot

Selector.open () // Crea el multiplexor socketChannel.register (selector, SelectionKey.OP_READ) // Registra el canal en el multiplexor selector.select () // Bloque esperando la ocurrencia de eventos que necesitan ser procesados

Resumen: Todo el proceso de llamada de NIO es que Java llama a la función del kernel del sistema operativo para crear un Socket, obtiene el descriptor de archivo del Socket y luego crea un objeto Selector, correspondiente al descriptor Epoll del sistema operativo, y la descripción del archivo de la conexión Socket obtenida El evento del símbolo está vinculado al descriptor de archivo Epoll correspondiente al Selector, y se realiza una notificación asincrónica del evento, lo que realiza el uso de un hilo y no requiere demasiados recorridos no válidos El procesamiento de eventos se transfiere al kernel del sistema operativo (operación El programa de interrupción del sistema se realiza), lo que mejora enormemente la eficiencia.

Explicación detallada de la función Epoll

int epoll_create (int tamaño);

Cree una instancia de epoll y devuelva un número no negativo como descriptor de archivo para todas las llamadas posteriores a la interfaz de epoll. El tamaño del parámetro representa que se pueden acomodar descriptores de tamaño, pero el tamaño no es un valor máximo, sino solo para indicar su orden de magnitud al sistema operativo. Ahora este parámetro está básicamente abandonado.

int epoll_ctl (int epfd, int op, int fd, struct epoll_event * event);

Utilice la instancia de epoll a la que hace referencia el descriptor de archivo epfd para realizar una operación de operación en el descriptor de archivo de destino fd.

El parámetro epfd representa el descriptor de archivo correspondiente a epoll y el parámetro fd representa el descriptor de archivo correspondiente al socket.

El parámetro op tiene los siguientes valores:

EPOLL_CTL_ADD: Registre el nuevo fd a dfpe y asocie el evento;

EPOLL_CTL_MOD: modifica el evento de monitoreo del fd registrado;

EPOLL_CTL_DEL: Elimina fd de epfd e ignora el evento vinculado, en este momento el evento puede ser nulo;

El evento de parámetro es una estructura

struct epoll_event {__uint32_t events; / * Eventos de Epoll * / epoll_data_t data; / * Variable de datos de usuario * /}; typedef union epoll_data {void * ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t;

Hay muchos valores opcionales para los eventos, estos son solo algunos de los más comunes:

EPOLLIN: Indica que el descriptor de archivo correspondiente es legible;

EPOLLOUT: Indica que se puede escribir en el descriptor de archivo correspondiente;

EPOLLERR: Indica que ocurrió un error en el descriptor de archivo correspondiente;

Devuelve 0 en caso de éxito, -1 en caso de fracaso

int epoll_wait (int epfd, struct epoll_event * events, int maxevents, int timeout);

Esperando un evento en el descriptor de archivo epfd.

epfd es el descriptor de archivo correspondiente a Epoll, eventos representa la colección de todos los eventos disponibles de la persona que llama, maxevents representa el número máximo de eventos que se devolverán y el tiempo de espera es el período de tiempo de espera.

La capa inferior de multiplexación de E / S es implementada principalmente por las funciones del kernel de Linux (select, poll, epoll), Windows no es compatible con epoll y la capa inferior de Windows se basa en la función de selección de winsock2 (no de código abierto)

 

Seleccione

encuesta

epoll (jdk 1.5 y superior)

Método de operación

atravesar

atravesar

Llamar de vuelta

Implementación de bajo nivel

Formación

Lista enlazada

Tabla de picadillo

Eficiencia IO

Cada llamada se atraviesa linealmente y la complejidad del tiempo es O (n)

Cada llamada se atraviesa linealmente y la complejidad del tiempo es O (n)

Método de notificación de eventos, siempre que un evento IO esté listo, se llamará a la función de devolución de llamada registrada por el sistema y la complejidad del tiempo es O (1)

Conexión máxima

Tapado

ilimitado

ilimitado

Modelo de subprocesamiento de Redis

Redis es un modelo típico de subproceso NIO basado en epoll (también para nginx). La instancia de epoll recopila todos los eventos (eventos de conexión y lectura-escritura), y un subproceso del servidor procesa continuamente todos los comandos de eventos.

El código fuente de epoll en la parte inferior de Redis está implementado en el archivo ae_epoll.c en el directorio fuente src de redis. Si está interesado, puede estudiarlo usted mismo.

AIO (NIO 2.0)

Asíncrono y sin bloqueo. Una vez completado el sistema operativo, se vuelve a llamar al programa del servidor para notificar al programa del servidor que inicie el proceso de subproceso. Por lo general, es adecuado para aplicaciones con una gran cantidad de conexiones y un tiempo de conexión prolongado.

Escenarios de aplicación:

El método AIO es adecuado para arquitecturas con una gran cantidad de conexiones y una conexión relativamente larga (operación pesada), y JDK7 comenzó a admitir

Ejemplo de código AIO:

package com.tuling.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AIOServer {
    public static void main(String[] args) throws Exception {
        final AsynchronousServerSocketChannel serverChannel =
                AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));

        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                try {
                    // 再此接收客户端连接,如果不写这行代码后面的客户端连接连不上服务端
                    serverChannel.accept(attachment, this);
                    System.out.println(socketChannel.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            buffer.flip();
                            System.out.println(new String(buffer.array(), 0, result));
                            socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer buffer) {
                            exc.printStackTrace();
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });

        Thread.sleep(Integer.MAX_VALUE);
    }
}

 

package com.tuling.aio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;

public class AIOClient {

    public static void main(String... args) throws Exception {
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
        socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
        ByteBuffer buffer = ByteBuffer.allocate(512);
        Integer len = socketChannel.read(buffer).get();
        if (len != -1) {
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        }
    }
}
/*
 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.nio.channels;

import java.nio.channels.spi.AsynchronousChannelProvider;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * A grouping of asynchronous channels for the purpose of resource sharing.
 *
 * <p> An asynchronous channel group encapsulates the mechanics required to
 * handle the completion of I/O operations initiated by {@link AsynchronousChannel
 * asynchronous channels} that are bound to the group. A group has an associated
 * thread pool to which tasks are submitted to handle I/O events and dispatch to
 * {@link CompletionHandler completion-handlers} that consume the result of
 * asynchronous operations performed on channels in the group. In addition to
 * handling I/O events, the pooled threads may also execute other tasks required
 * to support the execution of asynchronous I/O operations.
 *
 * <p> An asynchronous channel group is created by invoking the {@link
 * #withFixedThreadPool withFixedThreadPool} or {@link #withCachedThreadPool
 * withCachedThreadPool} methods defined here. Channels are bound to a group by
 * specifying the group when constructing the channel. The associated thread
 * pool is <em>owned</em> by the group; termination of the group results in the
 * shutdown of the associated thread pool.
 *
 * <p> In addition to groups created explicitly, the Java virtual machine
 * maintains a system-wide <em>default group</em> that is constructed
 * automatically. Asynchronous channels that do not specify a group at
 * construction time are bound to the default group. The default group has an
 * associated thread pool that creates new threads as needed. The default group
 * may be configured by means of system properties defined in the table below.
 * Where the {@link java.util.concurrent.ThreadFactory ThreadFactory} for the
 * default group is not configured then the pooled threads of the default group
 * are {@link Thread#isDaemon daemon} threads.
 *
 * <table border summary="System properties">
 *   <tr>
 *     <th>System property</th>
 *     <th>Description</th>
 *   </tr>
 *   <tr>
 *     <td> {@code java.nio.channels.DefaultThreadPool.threadFactory} </td>
 *     <td> The value of this property is taken to be the fully-qualified name
 *     of a concrete {@link java.util.concurrent.ThreadFactory ThreadFactory}
 *     class. The class is loaded using the system class loader and instantiated.
 *     The factory's {@link java.util.concurrent.ThreadFactory#newThread
 *     newThread} method is invoked to create each thread for the default
 *     group's thread pool. If the process to load and instantiate the value
 *     of the property fails then an unspecified error is thrown during the
 *     construction of the default group. </td>
 *   </tr>
 *   <tr>
 *     <td> {@code java.nio.channels.DefaultThreadPool.initialSize} </td>
 *     <td> The value of the {@code initialSize} parameter for the default
 *     group (see {@link #withCachedThreadPool withCachedThreadPool}).
 *     The value of the property is taken to be the {@code String}
 *     representation of an {@code Integer} that is the initial size parameter.
 *     If the value cannot be parsed as an {@code Integer} it causes an
 *     unspecified error to be thrown during the construction of the default
 *     group. </td>
 *   </tr>
 * </table>
 *
 * <a name="threading"></a><h2>Threading</h2>
 *
 * <p> The completion handler for an I/O operation initiated on a channel bound
 * to a group is guaranteed to be invoked by one of the pooled threads in the
 * group. This ensures that the completion handler is run by a thread with the
 * expected <em>identity</em>.
 *
 * <p> Where an I/O operation completes immediately, and the initiating thread
 * is one of the pooled threads in the group then the completion handler may
 * be invoked directly by the initiating thread. To avoid stack overflow, an
 * implementation may impose a limit as to the number of activations on the
 * thread stack. Some I/O operations may prohibit invoking the completion
 * handler directly by the initiating thread (see {@link
 * AsynchronousServerSocketChannel#accept(Object,CompletionHandler) accept}).
 *
 * <a name="shutdown"></a><h2>Shutdown and Termination</h2>
 *
 * <p> The {@link #shutdown() shutdown} method is used to initiate an <em>orderly
 * shutdown</em> of a group. An orderly shutdown marks the group as shutdown;
 * further attempts to construct a channel that binds to the group will throw
 * {@link ShutdownChannelGroupException}. Whether or not a group is shutdown can
 * be tested using the {@link #isShutdown() isShutdown} method. Once shutdown,
 * the group <em>terminates</em> when all asynchronous channels that are bound to
 * the group are closed, all actively executing completion handlers have run to
 * completion, and resources used by the group are released. No attempt is made
 * to stop or interrupt threads that are executing completion handlers. The
 * {@link #isTerminated() isTerminated} method is used to test if the group has
 * terminated, and the {@link #awaitTermination awaitTermination} method can be
 * used to block until the group has terminated.
 *
 * <p> The {@link #shutdownNow() shutdownNow} method can be used to initiate a
 * <em>forceful shutdown</em> of the group. In addition to the actions performed
 * by an orderly shutdown, the {@code shutdownNow} method closes all open channels
 * in the group as if by invoking the {@link AsynchronousChannel#close close}
 * method.
 *
 * @since 1.7
 *
 * @see AsynchronousSocketChannel#open(AsynchronousChannelGroup)
 * @see AsynchronousServerSocketChannel#open(AsynchronousChannelGroup)
 */

public abstract class AsynchronousChannelGroup {
    private final AsynchronousChannelProvider provider;

    /**
     * Initialize a new instance of this class.
     *
     * @param   provider
     *          The asynchronous channel provider for this group
     */
    protected AsynchronousChannelGroup(AsynchronousChannelProvider provider) {
        this.provider = provider;
    }

    /**
     * Returns the provider that created this channel group.
     *
     * @return  The provider that created this channel group
     */
    public final AsynchronousChannelProvider provider() {
        return provider;
    }

    /**
     * Creates an asynchronous channel group with a fixed thread pool.
     *
     * <p> The resulting asynchronous channel group reuses a fixed number of
     * threads. At any point, at most {@code nThreads} threads will be active
     * processing tasks that are submitted to handle I/O events and dispatch
     * completion results for operations initiated on asynchronous channels in
     * the group.
     *
     * <p> The group is created by invoking the {@link
     * AsynchronousChannelProvider#openAsynchronousChannelGroup(int,ThreadFactory)
     * openAsynchronousChannelGroup(int,ThreadFactory)} method of the system-wide
     * default {@link AsynchronousChannelProvider} object.
     *
     * @param   nThreads
     *          The number of threads in the pool
     * @param   threadFactory
     *          The factory to use when creating new threads
     *
     * @return  A new asynchronous channel group
     *
     * @throws  IllegalArgumentException
     *          If {@code nThreads <= 0}
     * @throws  IOException
     *          If an I/O error occurs
     */
    public static AsynchronousChannelGroup withFixedThreadPool(int nThreads,
                                                               ThreadFactory threadFactory)
        throws IOException
    {
        return AsynchronousChannelProvider.provider()
            .openAsynchronousChannelGroup(nThreads, threadFactory);
    }

    /**
     * Creates an asynchronous channel group with a given thread pool that
     * creates new threads as needed.
     *
     * <p> The {@code executor} parameter is an {@code ExecutorService} that
     * creates new threads as needed to execute tasks that are submitted to
     * handle I/O events and dispatch completion results for operations initiated
     * on asynchronous channels in the group. It may reuse previously constructed
     * threads when they are available.
     *
     * <p> The {@code initialSize} parameter may be used by the implementation
     * as a <em>hint</em> as to the initial number of tasks it may submit. For
     * example, it may be used to indicate the initial number of threads that
     * wait on I/O events.
     *
     * <p> The executor is intended to be used exclusively by the resulting
     * asynchronous channel group. Termination of the group results in the
     * orderly  {@link ExecutorService#shutdown shutdown} of the executor
     * service. Shutting down the executor service by other means results in
     * unspecified behavior.
     *
     * <p> The group is created by invoking the {@link
     * AsynchronousChannelProvider#openAsynchronousChannelGroup(ExecutorService,int)
     * openAsynchronousChannelGroup(ExecutorService,int)} method of the system-wide
     * default {@link AsynchronousChannelProvider} object.
     *
     * @param   executor
     *          The thread pool for the resulting group
     * @param   initialSize
     *          A value {@code >=0} or a negative value for implementation
     *          specific default
     *
     * @return  A new asynchronous channel group
     *
     * @throws  IOException
     *          If an I/O error occurs
     *
     * @see java.util.concurrent.Executors#newCachedThreadPool
     */
    public static AsynchronousChannelGroup withCachedThreadPool(ExecutorService executor,
                                                                int initialSize)
        throws IOException
    {
        return AsynchronousChannelProvider.provider()
            .openAsynchronousChannelGroup(executor, initialSize);
    }

    /**
     * Creates an asynchronous channel group with a given thread pool.
     *
     * <p> The {@code executor} parameter is an {@code ExecutorService} that
     * executes tasks submitted to dispatch completion results for operations
     * initiated on asynchronous channels in the group.
     *
     * <p> Care should be taken when configuring the executor service. It
     * should support <em>direct handoff</em> or <em>unbounded queuing</em> of
     * submitted tasks, and the thread that invokes the {@link
     * ExecutorService#execute execute} method should never invoke the task
     * directly. An implementation may mandate additional constraints.
     *
     * <p> The executor is intended to be used exclusively by the resulting
     * asynchronous channel group. Termination of the group results in the
     * orderly  {@link ExecutorService#shutdown shutdown} of the executor
     * service. Shutting down the executor service by other means results in
     * unspecified behavior.
     *
     * <p> The group is created by invoking the {@link
     * AsynchronousChannelProvider#openAsynchronousChannelGroup(ExecutorService,int)
     * openAsynchronousChannelGroup(ExecutorService,int)} method of the system-wide
     * default {@link AsynchronousChannelProvider} object with an {@code
     * initialSize} of {@code 0}.
     *
     * @param   executor
     *          The thread pool for the resulting group
     *
     * @return  A new asynchronous channel group
     *
     * @throws  IOException
     *          If an I/O error occurs
     */
    public static AsynchronousChannelGroup withThreadPool(ExecutorService executor)
        throws IOException
    {
        return AsynchronousChannelProvider.provider()
            .openAsynchronousChannelGroup(executor, 0);
    }

    /**
     * Tells whether or not this asynchronous channel group is shutdown.
     *
     * @return  {@code true} if this asynchronous channel group is shutdown or
     *          has been marked for shutdown.
     */
    public abstract boolean isShutdown();

    /**
     * Tells whether or not this group has terminated.
     *
     * <p> Where this method returns {@code true}, then the associated thread
     * pool has also {@link ExecutorService#isTerminated terminated}.
     *
     * @return  {@code true} if this group has terminated
     */
    public abstract boolean isTerminated();

    /**
     * Initiates an orderly shutdown of the group.
     *
     * <p> This method marks the group as shutdown. Further attempts to construct
     * channel that binds to this group will throw {@link ShutdownChannelGroupException}.
     * The group terminates when all asynchronous channels in the group are
     * closed, all actively executing completion handlers have run to completion,
     * and all resources have been released. This method has no effect if the
     * group is already shutdown.
     */
    public abstract void shutdown();

    /**
     * Shuts down the group and closes all open channels in the group.
     *
     * <p> In addition to the actions performed by the {@link #shutdown() shutdown}
     * method, this method invokes the {@link AsynchronousChannel#close close}
     * method on all open channels in the group. This method does not attempt to
     * stop or interrupt threads that are executing completion handlers. The
     * group terminates when all actively executing completion handlers have run
     * to completion and all resources have been released. This method may be
     * invoked at any time. If some other thread has already invoked it, then
     * another invocation will block until the first invocation is complete,
     * after which it will return without effect.
     *
     * @throws  IOException
     *          If an I/O error occurs
     */
    public abstract void shutdownNow() throws IOException;

    /**
     * Awaits termination of the group.

     * <p> This method blocks until the group has terminated, or the timeout
     * occurs, or the current thread is interrupted, whichever happens first.
     *
     * @param   timeout
     *          The maximum time to wait, or zero or less to not wait
     * @param   unit
     *          The time unit of the timeout argument
     *
     * @return  {@code true} if the group has terminated; {@code false} if the
     *          timeout elapsed before termination
     *
     * @throws  InterruptedException
     *          If interrupted while waiting
     */
    public abstract boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
}

Comparación de BIO, NIO, AIO:

¿Por qué Netty usa NIO en lugar de AIO?

En los sistemas Linux, la implementación subyacente de AIO todavía usa Epoll, y AIO no está bien implementado, por lo que no hay una ventaja obvia en el rendimiento, está encapsulado por JDK y no es fácil de optimizar profundamente. AIO en Linux no está maduro suficiente. Netty es un marco asincrónico sin bloqueo, y Netty ha realizado una gran cantidad de encapsulación asincrónica en NIO.

Sincrónico, asincrónico y sin bloqueo de bloqueo

A Lao Zhang le encanta beber té, no decir tonterías, hervir agua.

Caracteres: Lao Zhang, dos hervidores (hervidor ordinario, denominado hervidor; hervidor que suena, denominado hervidor que suena).

1 Lao Zhang puso la tetera al fuego e inmediatamente esperó a que el agua hirviera. (Bloqueo sincrónico)

Lao Zhang se siente un poco estúpido

2 Lao Zhang puso la tetera al fuego, fue a la sala de estar a ver la televisión y de vez en cuando iba a la cocina para ver si el agua estaba hirviendo. (Sin bloqueo sincrónico)

Lao Zhang todavía se sentía un poco estúpido, por lo que se volvió lujoso y compró una tetera que puede hacer sonar una flauta. Después de hervir el agua, puede hacer un ruido fuerte.

3 Lao Zhang puso la tetera al fuego y esperó a que el agua hirviera. (Bloqueo asincrónico)

Lao Zhang se siente tan estúpido que tiene poco sentido

4 Lao Zhang puso la tetera al fuego y fue a la sala de estar para ver la televisión. Dejó de verla antes de que sonara la tetera y fue a buscarla cuando sonó. (Sin bloqueo asincrónico)

Lao Zhang pensó que era inteligente.

El llamado síncrono y asíncrono es solo para el hervidor.

Hervidor ordinario, sincrónico; hervidor sonando, asincrónico.

Aunque pueden funcionar, el sonido de la tetera puede recordarle a Lao Zhang que el agua está hirviendo después de que termina el trabajo. Esto está más allá del alcance de los hervidores comunes.

La sincronización solo puede permitir que la persona que llama se interrogue a sí mismo (en el caso 2), lo que provoca la ineficiencia de Zhang.

El llamado bloqueo no bloqueo es solo para Lao Zhang.

El viejo Zhang de pie está bloqueando; el viejo Zhang viendo la televisión es sin bloqueo.

Supongo que te gusta

Origin blog.csdn.net/nmjhehe/article/details/114740223
Recomendado
Clasificación