Multithreading em Java
A Java Virtual Machine suporta computação paralela . Todos os cálculos podem ser executados no contexto de um ou mais threads. Podemos configurar facilmente o acesso ao mesmo recurso ou objeto para vários threads, bem como configurar um thread para executar um único bloco de código.
Qualquer desenvolvedor precisa sincronizar o trabalho com threads durante operações de leitura e gravação para recursos que possuem vários threads alocados para eles.
É importante que no momento de acessar o recurso você tenha dados atualizados para que outro thread possa alterá-lo e você obtenha as informações mais atualizadas. Mesmo que tomemos o exemplo de uma conta bancária, até que o dinheiro chegue a ela, você não pode usá-la, por isso é importante ter dados sempre atualizados. Java tem classes especiais para sincronizar e gerenciar threads.
Objetos de encadeamento
Tudo começa com o thread principal (principal), ou seja, pelo menos seu programa já possui um thread em execução. O thread principal pode criar outros threads usando Callable ou Runnable . A criação difere apenas no resultado de retorno, Runnable não retorna um resultado e não pode lançar uma exceção verificada. Portanto, você tem uma boa oportunidade de construir um trabalho eficiente com arquivos, mas isso é muito perigoso e você precisa ter cuidado.
Também é possível agendar a execução do thread em um núcleo de CPU separado. O sistema pode se mover facilmente entre as threads e executar uma thread específica com as configurações corretas: ou seja, a thread que lê os dados é executada primeiro, assim que tivermos os dados, depois passamos para a thread responsável pela validação, depois disso, passamos para o thread para executar alguma lógica de negócios e um novo thread os escreve de volta. Em tal situação, 4 threads estão processando dados por vez e tudo funcionará mais rápido do que um thread. Cada um desses fluxos é convertido em um fluxo de sistema operacional nativo, mas como ele será convertido depende da implementação da JVM.
A classe Thread é usada para criar e trabalhar com threads. Possui mecanismos de controle padrão, bem como abstratos, como classes e coleções de java.util.concurrent .
Sincronização de Threads em Java
A comunicação é fornecida pelo compartilhamento de acesso aos objetos. Isso é muito eficaz, mas ao mesmo tempo é muito fácil cometer erros ao trabalhar. Erros ocorrem em dois casos: interferência de encadeamento - quando outro encadeamento interfere no seu encadeamento e erros de consistência de memória - consistência de memória. Para resolver e prevenir esses erros, temos diferentes métodos de sincronização.
A sincronização de threads em Java é feita por monitores, este é um mecanismo de alto nível que permite que apenas um thread execute um bloco de código protegido pelo mesmo monitor por vez. O comportamento dos monitores é considerado em termos de bloqueios; um monitor - um bloqueio.
A sincronização tem vários pontos importantes aos quais você precisa prestar atenção. O primeiro ponto é a exclusão mútua - apenas um thread pode possuir o monitor, portanto, a sincronização no monitor implica que uma vez que um thread entra em um bloco sincronizado protegido pelo monitor, nenhum outro thread pode entrar no bloco protegido pelo monitor. primeiro thread sai do bloco sincronizado. Ou seja, vários threads não podem acessar o mesmo bloco sincronizado ao mesmo tempo.
Mas a sincronização não é apenas exclusão mútua. A sincronização garante que os dados gravados na memória antes ou dentro de um bloco sincronizado se tornem visíveis para outros threads sincronizados no mesmo monitor. Após sair do bloco, liberamos o monitor e outra thread pode agarrá-lo e começar a executar este bloco de código.
Quando um novo thread captura o monitor, ganhamos acesso e a capacidade de executar esse bloco de código e, nesse momento, as variáveis serão carregadas da memória principal. Então podemos ver todas as entradas tornadas visíveis pela versão anterior do monitor.
Uma leitura ou gravação individual de um campo é atômica se o campo for declarado como volatile (isso é importante para long e double) ou se todos os acessos a ele forem protegidos pelo mesmo bloqueio. Sem sincronização, podem ocorrer efeitos de reordenação (reordering): em um programa sincronizado incorretamente, um thread pode ver as alterações feitas por outro thread em uma ordem inesperada.
O efeito de exclusão mútua e sincronização de threads, ou seja, sua operação correta é obtida apenas inserindo um bloco ou método sincronizado que adquira implicitamente um bloqueio ou obtendo explicitamente um bloqueio. Falaremos sobre isso abaixo. Ambas as formas de trabalhar afetam sua memória e é importante não esquecer de trabalhar com variáveis voláteis .
Campos voláteis em Java
A palavra-chave volatile resolve o problema da visibilidade. Se um thread gravou um valor em um campo volatile, qualquer outro thread que leia esse campo depois verá o novo valor, e não uma cópia desatualizada.
Em termos de efeito na memória, a gravação em um campo volatile é parecida com a liberação de um monitor, e a leitura, com a sua aquisição. Tudo o que o thread A gravou antes de gravar no campo volatile é garantidamente visto pelo thread B, se ele tiver lido desse campo o valor gravado. Essa relação se chama happens-before ("acontece antes").
Uma leitura ou gravação individual de um campo volatile é atômica, inclusive para long e double. Já as operações compostas, como count++ (ler, incrementar, gravar), não são: outro thread pode se intrometer entre as etapas, e parte dos incrementos se perde. O volatile não oferece exclusão mútua. Para que uma operação composta seja executada de forma atômica, são necessários synchronized, Lock ou classes como AtomicInteger, AtomicLong etc.
Java Simultâneo
Se você deseja criar um aplicativo supereficiente e multiencadeado, deve usar as classes da biblioteca JavaConcurrent , que estão no pacote java.util.concurrent .
A biblioteca é muito volumosa e possui diversas funcionalidades, então vamos dar uma olhada no que tem dentro e dividir em alguns módulos:
Coleções simultâneas são um conjunto de coleções para trabalhar em um ambiente multiencadeado. Em vez do wrapper básico Collections.synchronizedList com bloqueio de acesso a toda a coleção, bloqueios são usados em segmentos de dados ou algoritmos sem espera são usados para ler dados em paralelo.
Filas - filas sem bloqueio e com bloqueio para trabalhar em um ambiente multiencadeado. As filas sem bloqueio concentram-se na velocidade e na operação sem bloqueio de threads. As filas de bloqueio são adequadas para o trabalho quando você precisa “desacelerar” os encadeamentos do produtor ou do consumidor . Por exemplo, em uma situação em que algumas das condições não são atendidas, a fila está vazia ou cheia ou não há Consumidor 'a livre.
Os sincronizadores são utilitários para sincronizar threads. Eles são uma arma poderosa na computação "paralela".
Executors é uma estrutura para criação mais conveniente e fácil de pools de threads, é fácil configurar o agendamento de tarefas assíncronas com a obtenção de resultados.
Os bloqueios são muitos mecanismos flexíveis de sincronização de encadeamento em comparação com o sincronizado básico , aguarde , notifique e notifiqueTodos .
Atomics são classes que podem suportar operações atômicas em primitivas e referências.
GO TO FULL VERSION