Modelos de encadeamento em Java

Modelos de encadeamento em Java

1. Introdução

Freqüentemente, em nossos aplicativos, precisamos ser capazes de fazer várias coisas ao mesmo tempo. Podemos conseguir isso de várias maneiras, mas a chave entre elas é implementar a multitarefa de alguma forma.

Multi-tasking means running multiple tasks at the same time, onde cada tarefa está realizando seu trabalho. Essas tarefas geralmente são executadas ao mesmo tempo, lendo e gravando a mesma memória e interagindo com os mesmos recursos, mas fazendo coisas diferentes.

2. Tópicos nativos

The standard way of implementing multi-tasking in Java is to use threads. Geralmente, o encadeamento é suportado no sistema operacional. Chamamos de threads que funcionam nesse nível de "threads nativos".

O sistema operacional possui algumas habilidades com threading que geralmente não estão disponíveis para nossos aplicativos, simplesmente por causa da proximidade com o hardware subjacente. Isso significa que a execução de threads nativos geralmente é mais eficiente. Esses threads são mapeados diretamente para os threads de execução na CPU do computador - e o sistema operacional gerencia o mapeamento de threads nos núcleos da CPU.

The standard threading model in Java, covering all JVM languages, uses native threads. Esse é o caso desde o Java 1.2 e é o caso, independentemente do sistema subjacente no qual a JVM está sendo executada.

Isso significa que sempre que usamos qualquer um dos mecanismos de threading padrão em Java, estamos usando threads nativos. Isso incluijava.lang.Thread,java.util.concurrent.Executor,java.util.concurrent.ExecutorService e assim por diante.

3. Fios verdes

Em engenharia de software,one alternative to native threads is green threads. É aqui que estamos usando threads, mas eles não são mapeados diretamente para os threads do sistema operacional. Em vez disso, a arquitetura subjacente gerencia os próprios threads e gerencia como eles são mapeados para os threads do sistema operacional.

Typically this works by running several native threads and then allocating the green threads onto these native threads for execution. O sistema pode escolher em quais threads verdes estão ativos a qualquer momento e em quais threads nativos eles estão ativos.

Isso parece muito complicado, e é. Mas é uma complicação com a qual geralmente não precisamos nos preocupar. A arquitetura subjacente cuida de tudo isso e podemos usá-la como se fosse um modelo de encadeamento nativo.

Então, por que faríamos isso? Os encadeamentos nativos são muito eficientes de executar, mas têm um alto custo para iniciá-los e pará-los. Threads verdes ajudam a evitar esse custo e dão à arquitetura muito mais flexibilidade. Se estivermos usando threads de execução relativamente longa, os threads nativos serão muito eficientes. For very short-lived jobs, the cost of starting them can outweigh the benefit of using them. Nesses casos, os fios verdes podem se tornar mais eficientes.

Infelizmente,Java does not have built-in support for green threads.

Versões muito antigas usavam threads verdes em vez de threads nativos como o modelo de encadeamento padrão. Isso mudou no Java 1.2 e, desde então, não há suporte para ele no nível da JVM.

Também é um desafio implementar threads verdes em bibliotecas porque elas precisam de suporte de nível muito baixo para ter um bom desempenho. Como tal, uma alternativa comum usada são as fibras.

4. Fibras

Fibers are an alternative form of multi-threading and are similar to green threads. Em ambos os casos, não estamos usando threads nativos e, em vez disso, estamos usando os controles do sistema subjacentes que estão em execução a qualquer momento. A grande diferença entre fios e fibras verdes está no nível de controle e, especificamente, quem está no controle.

Linhas verdes são uma forma de multitarefa preemptiva. Isso significa que a arquitetura subjacente é totalmente responsável por decidir quais threads estão executando a qualquer momento.

Isso significa que todos os problemas usuais de threading se aplicam, onde não sabemos nada sobre a ordem de execução de nossos threads, ou quais estarão executando ao mesmo tempo. Isso também significa que o sistema subjacente precisa poder pausar e reiniciar nosso código a qualquer momento, potencialmente no meio de um método ou mesmo de uma instrução.

Fibers are instead a form of cooperative multitasking, meaning that a running thread will continue to run until it signals that it can yield to another. Isso significa que é nossa responsabilidade que as fibras cooperem entre si. Isso nos coloca no controle direto sobre quando as fibras podem pausar a execução, em vez de o sistema decidir isso por nós.

Isso também significa que precisamos escrever nosso código de uma maneira que permita isso. Caso contrário, não funcionará. Se nosso código não tem nenhum ponto de interrupção, então podemos muito bem não estar usando fibras.

Atualmente, o Java não possui suporte interno para fibras. Existem algumas bibliotecas que podem introduzir isso em nossos aplicativos, incluindo, entre outros:

4.1. Quasar

Quasar é uma biblioteca Java que funciona bem com Java puro e Kotlin e tem uma versão alternativa que funciona com Clojure.

Ele funciona com um agente Java que precisa ser executado juntamente com o aplicativo, e esse agente é responsável por gerenciar as fibras e garantir que elas funcionem juntas corretamente. O uso de um agente Java significa que não há etapas especiais de construção necessárias.

O Quasar também requer que o Java 11 funcione corretamente para limitar os aplicativos que podem usá-lo. Versões mais antigas podem ser usadas no Java 8, mas elas não são ativamente suportadas.

4.2. Kilim

Kilim is a Java library that offers very similar functionality to Quasar but does so by using bytecode weaving instead of a Java agent. Isso significa que ele pode funcionar em mais locais, mas torna o processo de criação mais complicado.

Kilim trabalha com Java 7 e mais recente e funcionará corretamente mesmo em cenários em que um agente Java não é uma opção. Por exemplo, se um outro já estiver sendo usado para instrumentação ou monitoramento.

4.3. Projeto tear

Project Loom is an experiment by the OpenJDK project to add fibers to the JVM itself, rather than as an add-on library. Isso nos dará as vantagens das fibras sobre os fios. Ao implementá-lo diretamente na JVM, pode ajudar a evitar complicações que os agentes Java e a tecelagem de códigos de código introduzem.

Não há cronograma de lançamento atual para o Project Loom, mas podemos fazer o download de binários de acesso antecipado agora para ver como estão as coisas. No entanto, como ainda é muito cedo, precisamos ter cuidado com isso para qualquer código de produção.

5. Co-Rotinas

As co-rotinas são uma alternativa para rosqueamento e fibras. We can think of co-routines as fibers without any form of scheduling. Em vez de o sistema subjacente decidir quais tarefas estão sendo executadas a qualquer momento, nosso código faz isso diretamente.

Geralmente, escrevemos co-rotinas para que elas produzam em pontos específicos de seu fluxo. Estes podem ser vistos como pontos de pausa em nossa função, onde parará de funcionar e potencialmente produzirá algum resultado intermediário. Quando produzimos, somos parados até o código de chamada decidir nos reiniciar por qualquer motivo. This means that our calling code controls the scheduling of when this will run.

O Kotlin possui suporte nativo para co-rotinas integradas em sua biblioteca padrão. Existem várias outras bibliotecas Java que podemos usar para implementá-las, se desejado.

6. Conclusão

Vimos várias alternativas diferentes para multitarefa em nosso código, desde os threads nativos tradicionais a algumas alternativas muito leves. Por que não experimentá-los na próxima vez que um aplicativo precisar de simultaneidade?