[JAVA Basics] - Explicação detalhada do modo síncrono sem bloqueio NIO

[JAVA Basics] - Explicação detalhada do modo síncrono sem bloqueio NIO

I. Visão geral

NIO (Non-Blocking IO) é uma forma síncrona e sem bloqueio de processar dados IO. O modo de implementação do servidor é uma solicitação e um thread, ou seja, a solicitação de link enviada pelo cliente será registrada no seletor, e o seletor iniciará um thread para processamento somente quando houver uma solicitação de IO na conexão.

2. Conceitos comuns

  • Síncrono : O método de chamada refere-se ao Aplicativo. Quando o chamador inicia uma chamada de função, a chamada não retornará até que o resultado da função seja obtido. Ou seja, o chamador esperará que o receptor retorne o resultado da função.
  • Assíncrono : quando o chamador inicia uma chamada de função, o resultado da função que não é obtido é retornado imediatamente.Posteriormente, o chamador notifica o chamador sobre a estrutura da função por meio de retornos de chamada e outros meios. Ou seja, o chamador obtém o retorno imediatamente, mas o retorno não inclui os resultados da execução.

A sincronização e a comunicação assíncrona enfatizam o mecanismo de comunicação de mensagens (comunicação síncrona/comunicação assíncrona). A chamada sincronização significa que quando uma “chamada” for emitida, a “chamada” não retornará até que o resultado seja obtido. Mas assim que a chamada retornar, você obterá o valor de retorno. Em outras palavras, o “chamador” espera ativamente pelo resultado desta “chamada”. Assíncrono é o oposto: após a emissão da "chamada", a chamada retorna diretamente, portanto nenhum resultado é retornado. Em outras palavras, quando uma chamada de procedimento assíncrona é emitida, o chamador não obtém o resultado imediatamente. Em vez disso, após a "chamada" ser emitida, o "chamado" notifica o chamador por meio de status e notificação ou trata a chamada por meio de uma função de retorno de chamada.

  • Bloqueio : Quando um thread inicia uma chamada, o thread será bloqueado antes que a chamada retorne. Nesse estado, os direitos atuais de uso da CPU serão entregues e suspensos, ou seja, o chamador aguardará o resultado da chamada e a chamada bloqueia o thread do chamador. O thread não está executando o processamento.
  • Sem bloqueio : quando um thread inicia uma chamada, a chamada retornará imediatamente para evitar que o thread seja bloqueado. No entanto,o resultado devolvido é apenas o valor do estado actual do chamado.Quando realmente utilizado,o chamador precisa de sondar até que o resultado devolvido corresponda às expectativas (até que os dados estejam prontos).

Bloqueio e não bloqueio enfatizam o estado do programa enquanto aguarda o resultado da chamada (mensagem, valor de retorno).Bloquear chamada significa que o thread atual será suspenso antes que o resultado da chamada seja retornado. O thread de chamada retornará somente após obter o resultado. Uma chamada sem bloqueio significa que a chamada não bloqueará o thread atual até que o resultado não possa ser obtido imediatamente. Para chamadas síncronas, muitas vezes a thread atual ainda está ativa, mas logicamente a função atual não retorna, ou seja, nada é feito enquanto espera de forma síncrona, ocupando recursos em vão.

  • Bloqueio síncrono IO [BIO - BlockingIO] : Neste método, após iniciar uma operação IO, o processo do usuário deve aguardar a conclusão da operação IO. Somente quando a operação IO for realmente concluída, o processo do usuário poderá ser executado. O modelo IO tradicional de JAVA pertence a este método.
  • IO sem bloqueio síncrono [IO sem bloqueio] : Neste método, o processo do usuário inicia uma operação de IO e pode retornar para fazer outras coisas mais tarde, mas o processo do usuário precisa perguntar de tempos em tempos se a operação de IO está pronta, o que exige que o processo do usuário continue solicitando, introduzindo assim desperdício desnecessário de recursos da CPU. Entre eles, o NIO do JAVA atualmente pertence ao IO síncrono e sem bloqueio.
  • Bloqueio assíncrono IO [IO Multiplexing] : Este método significa que após o aplicativo iniciar uma operação IO, ele não aguardará a conclusão da operação IO do kernel. Depois que o kernel concluir a operação IO, ele notificará o aplicativo. Na verdade, isso é a diferença mais crítica entre síncrono e assíncrono., a sincronização deve esperar ou perguntar ativamente se o IO foi concluído, então por que ele está bloqueado? Porque isso é feito através da chamada do sistema select, e a implementação da própria função select é bloqueada.A vantagem de usar a função select é que ela pode monitorar vários identificadores de arquivos ao mesmo tempo, melhorando assim a simultaneidade do sistema!
  • IO assíncrono sem bloqueio [IO assíncrono]: Neste modo, o processo do usuário só precisa iniciar uma operação IO e retornar imediatamente. Depois que a operação IO for realmente concluída, o aplicativo será notificado de que a operação IO foi concluída. Em desta vez, o processo do usuário só precisa. Enquanto os dados precisarem ser processados, as operações reais de leitura e gravação de IO não são necessárias, porque as operações reais de leitura ou gravação de IO foram concluídas pelo kernel. Atualmente não há suporte para este modelo IO em Java.

3. Princípio de Implementação do NIO

Insira a descrição da imagem aqui

O NIO do Java é composto principalmente de três partes principais: Channel , Buffer e Selector .

Todo IO começa a partir de um canal no NIO. Os dados podem ser lidos do canal para o buffer ou gravados do buffer para o canal. Existem vários tipos de canais, entre os quais os mais utilizados são FileChannel , DatagramChannel , SocketChannel , ServerSocketChannel , etc. Esses canais cobrem IO de rede UDP e TCP e IO de arquivo.

Um Buffer é essencialmente um bloco de memória no qual os dados podem ser gravados e a partir do qual os dados podem ser lidos. Essa memória é empacotada como um objeto NIO Buffer e fornece um conjunto de métodos para acessar essa memória de maneira conveniente. As principais implementações de Buffer em Java NIO incluem CharBuffer , ByteBuffer , ShortBuffer , IntBuffer , LongBuffer , FloatBuffer e DoubleBuffer . Esses buffers cobrem os tipos de dados básicos que você pode enviar por meio de IO, ou seja, byte , short , int , long , float , double , char .

O objeto Buffer contém três atributos importantes, nomeadamente capacidade , posição e limite.Os significados de posição e limite dependem se o Buffer está no modo de leitura ou no modo de gravação. Mas não importa em que modo o Buffer esteja, o significado de capacidade é sempre o mesmo.

Capacidade : Como bloco de memória, o Buffer possui um valor máximo fixo, que é a capacidade. O buffer só pode gravar capacidade de dados. Quando o buffer estiver cheio, ele precisará ser esvaziado antes de poder continuar a gravar dados.

position : Ao gravar dados no Buffer, a posição representa a posição atual. O valor da posição inicial é 0. Quando um dado é gravado no Buffer, a posição avança para a próxima unidade do Buffer onde os dados podem ser inseridos. A posição máxima pode ser capacidade-1. Quando os dados são lidos, eles também são lidos de um local específico. Ao mudar o Buffer do modo de gravação para o modo de leitura, a posição será redefinida para 0. Quando os dados são lidos da posição do Buffer, a posição avança para a próxima posição legível.

limite : No modo de gravação, o limite do Buffer indica a quantidade máxima de dados que podem ser gravados no Buffer. Neste momento, o limite é igual à capacidade. Ao mudar o Buffer para o modo de leitura, o limite indica a quantidade máxima de dados que você pode ler. Neste momento, o limite será definido para o valor da posição no modo de gravação.

O seletor permite que um único thread processe vários canais. Se sua aplicação abre várias conexões (canais), mas o tráfego de cada conexão é muito baixo, usar o seletor será muito conveniente. Para usar um Seletor, você deve registrar um Canal com o Seletor e então chamar seu método select(). Este método será bloqueado até que um canal registrado tenha um evento pronto. Assim que esse método retornar, o thread poderá lidar com esses eventos, como entrada de novas conexões, recepção de dados, etc.

4. Implementação do código NIO

Implementação do cliente
  • etapa

    1. Criar canal SocketChannel
    2. Alternar modo assíncrono sem bloqueio configureBlocking(false)
    3. Definir tamanho do buffer ByteBuffer.allocate(1024)
    4. Os valores são gravados no buffer buffer.put(input.getBytes())
    5. O valor no buffer é gravado no canal channel.write()
  • demonstração de código

    public static void main(String[] args) throws IOException {
          
          
          //创建通道
          SocketChannel channel=SocketChannel.open(new InetSocketAddress("127.0.0.1",6001));
          //切换异步非阻塞模式
          channel.configureBlocking(false);
          //设置缓冲去大小
          ByteBuffer buffer=ByteBuffer.allocate(1024);
          System.out.println("输入传输值:");
          //获取键盘输入的值
          Scanner scanner = new Scanner(System.in);
          while (scanner.hasNext()){
          
          
              String input=scanner.next();
              //把获取的值写入缓冲区中
              buffer.put(input.getBytes());
              buffer.flip();
              //把缓冲区中的值写入通道中
              channel.write(buffer);
              buffer.clear();
           }
           channel.close();
     }
    
Implementação do lado do servidor
  • etapa

    1. Criar canal ServerSocketChannel
    2. Alternar modo assíncrono sem bloqueio configureBlocking(false)
    3. Vincular conexão
    4. Obtenha o selectorSelector aberto = Selector.open()
    5. Registre o canal no seletor e especifique para ouvir eventos de aceitação
    6. Aquisição round-robin de eventos selecionados que estão prontos
    7. Obtenha todos os eventos de escuta registrados do seletor atual
    8. Prepare-se para eventos
    9. Determine quais eventos estão prontos
    10. Aceite pronto, obtenha conexão do cliente
    11. Definir modo assíncrono sem bloqueio
    12. Registre o canal no servidor
  • demonstração de código

    public static void main(String[] args) throws IOException {
          
          
        //创建通道
        ServerSocketChannel channel=ServerSocketChannel.open();
        //切换到异步非阻塞模式
        channel.configureBlocking(false);
        //绑定链接
        channel.bind(new InetSocketAddress(6001));
            //获取选择器
            Selector open = Selector.open();
            //将通道注册到选择器,并指定监听接受事件
            channel.register(open, SelectionKey.OP_ACCEPT);
            //轮训式获取选择已经准备就绪的事件
            while(open.select() > 0) {
          
          
                //获取当前选择器所有注册的监听事件
                Iterator<SelectionKey> it = open.selectedKeys().iterator();
                while(it.hasNext()) {
          
          
                    //获取准备就绪的事件
                    SelectionKey sk = it.next();
                    //判断是什么事件准备就绪
                    if(sk.isAcceptable()) {
          
          
                        //接受就绪,获取客户端连接
                        SocketChannel sc = channel.accept();
                        //设置非阻塞异步模式
                        sc.configureBlocking(false);
                        //将通道注册到服务器上
                        sc.register(open, SelectionKey.OP_READ);
                    } else if(sk.isReadable()) {
          
          
                        //获取当前选择器就绪的通道
                        SocketChannel s =  (SocketChannel) sk.channel();
                        ByteBuffer bb = ByteBuffer.allocate(1024);
                        int len = 0;
                        while((len = s.read(bb)) > 0) {
          
          
                            bb.flip();
                            System.out.println(new String(bb.array(),0,len));
                            bb.clear();
                        }
                   }
              }
             it.remove();
         }
    
    }
    

5. Resumo do NIO síncrono e sem bloqueio

Características síncronas de não bloqueio : O thread do aplicativo precisa fazer chamadas de sistema IO continuamente e pesquisar se os dados estão prontos. Caso contrário, continue a pesquisa até que a chamada do sistema IO seja concluída.

Características do IO síncrono sem bloqueio : Cada chamada do sistema IO iniciada pode retornar imediatamente enquanto o kernel aguarda dados. O thread do usuário não será bloqueado e o desempenho em tempo real será melhor.

Desvantagens do IO síncrono sem bloqueio: pesquisa constante do kernel, o que ocupará muito tempo da CPU e é ineficiente.

De modo geral, a E/S síncrona sem bloqueio não está disponível em cenários de aplicativos de alta simultaneidade. Servidores web gerais não são adequados para este modelo IO. Este modelo de IO geralmente raramente é usado diretamente, mas o recurso de IO sem bloqueio é usado em outros modelos de IO.

Acho que você gosta

Origin blog.csdn.net/songjianlong/article/details/132223450
Recomendado
Clasificación