Prefácio
Qualquer pessoa que esteja familiarizada com a programação simultânea Java sabe que a regra de acontecimento antes (HB) no JMM (Java Memory Model), que define a ordem e a visibilidade das operações multi-tibos Java, impedindo o impacto da reordenação do compilador nos resultados do programa.
Há uma regra de "quando antes" na língua java. É uma relação de ordem parcial entre duas operações definidas no modelo de memória Java. Se a operação A ocorrer primeiro na operação B, significa que antes da operação B ocorrer, o impacto da operação A pode ser observado pela Operação B. A "influência" inclui a modificação do valor das variáveis compartilhadas na memória, enviando mensagens, métodos de chamada etc., que basicamente não tem relação com a sequência de ocorrência no tempo. Este princípio é particularmente importante. É a principal base para julgar se há concorrência nos dados e se os threads são seguros.
De acordo com o comunicado oficial:
Quando uma variável é lida por vários threads e gravada por pelo menos um thread, se não houver relação HB entre operações de leitura e gravação, surgirão problemas de corrida de dados.
Para garantir que o encadeamento que opere B veja o resultado da operação A (independentemente de A e B estarem no mesmo encadeamento), o princípio da HB deve ser atendido entre A e B e, se não, pode levar à reordenação.
Quando falta o relacionamento da HB, podem ocorrer problemas de reordenação.
Quais são as regras para o HB?
Todo mundo está muito familiarizado com isso. A maioria dos livros e artigos serão introduzidos. Vamos analisá -lo brevemente aqui:
Entre eles, eu ouce as regras de entrega, o que é crucial. Como usar as regras de entrega de forma proficiente é a chave para obter a sincronização.
Em seguida, explique a HB de outra perspectiva: quando uma operação A HB opera B, o resultado da operação da operação A na variável compartilhada é visível para a operação B.
Ao mesmo tempo, se a Operação B HB operar C, o resultado da operação da operação A na variável compartilhada é visível para a operação B.
O princípio de alcançar a visibilidade é o protocolo de cache e a barreira da memória. A visibilidade é alcançada através de protocolos de coerência de cache e barreiras de memória.
Como alcançar a sincronização?
No livro de Doug Lea "Java Concurrency in Practice", a descrição a seguir é:
O livro menciona: Ao combinar algumas regras de HB, a visibilidade de uma variável protegida desbloqueada pode ser alcançada.
Mas como essa técnica é sensível à ordem das declarações, ela é propensa a erros.
Em seguida, o autor demonstrará como sincronizar uma variável por meio de regras voláteis e regras de ordem do programa.
Vamos ter um exemplo familiar:
classe ThreadPrintDemo {static int num = 0; bandeira booleana volátil estática = false; public static void main (string [] args) {thread t1 = novo thread (() -> {for (; 100> num;) {if (! sinalizador && (num == 0 || ++ num % 2 == 0)) {System.out.println (num); flag = true;}}}); Thread t2 = novo thread (() -> {for (; 100> num;) {if (sinalizador && (++ num % 2! = 0)) {System.out.println (num); flag = false;}}}); t1.start (); t2.start (); }}O objetivo deste código é imprimir números 0 - 100 entre dois threads.
Os alunos familiarizados com a programação simultânea devem dizer que essa variável NUM não usa volátil, e haverá problemas de visibilidade, ou seja, o thread T1 atualizou o NUM e o thread T2 não pode percebê -lo.
Haha, o autor pensou assim no começo, mas recentemente ao estudar as regras do HB, descobri que não há problema em remover a modificação volátil de Num.
Vamos analisá -lo e o pôster desenhou uma foto:
Vamos analisar esta figura:
Nota: A regra da HB garante que os resultados da operação anterior sejam visíveis para a próxima operação.
Portanto, no applet acima, o encadeamento B está totalmente ciente da modificação de NUM por thread a - mesmo que o NUM não seja modificado com volátil.
Dessa maneira, usamos o princípio da HB para realizar a operação síncrona de uma variável, ou seja, em um ambiente multithread, garantimos a segurança da modificação simultânea de variáveis compartilhadas. E não há primitivas Java para essa variável: volátil e sincronizada e CAS (assumindo que conta).
Isso pode parecer inseguro (realmente seguro) e pode não parecer fácil de entender. Porque tudo isso é implementado pelo protocolo de cache e barreira de memória no HB subjacente.
Outras regras para alcançar a sincronização
Implementação usando regras de terminação de threads:
estático int a = 1; public static void main (string [] args) {thread tb = novo thread (() -> {a = 2;}); Thread ta = novo thread (() -> {tente {tb.join ();} catch (interruptedException e) {// no} system.out.println (a);}); ta.start (); tb.start (); } Use regras de início do thread para implementar:
estático int a = 1; public static void main (string [] args) {thread tb = novo thread (() -> {System.out.println (a);}); Thread ta = novo thread (() -> {tb.start (); a = 2;}); ta.start (); }Essas duas operações também podem garantir a visibilidade da variável a.
Realmente subverte o conceito anterior. No conceito anterior, se uma variável não for modificada por volátil ou final, sua leitura e escrita em multi -threading são definitivamente inseguras - porque haverá caches, resultando na leitura da qual não é a mais recente.
No entanto, usando a HB, podemos alcançá -lo.
Resumir
Embora o título deste artigo seja realizar operações síncronas de variáveis compartilhadas por meio de acontecimentos, o principal objetivo é entender o que acontecer antes de mais profundamente. Compreender seu conceito de acontecer antes de garantir a ordem da operação anterior para a próxima operação e a visibilidade da operação resulta em um ambiente multithread.
Ao mesmo tempo, usando as regras transitivas de maneira flexível e, em seguida, combinando regras, dois threads podem ser sincronizados - a implementação da variável compartilhada especificada sem usar primitivas também pode garantir a visibilidade. Embora isso não pareça muito fácil de ler, também é uma tentativa.
Doug Lea dá prática em JUC sobre como combinar regras para alcançar a sincronização.
Por exemplo, a sincronização da classe interna da versão antiga do FutureTask (desapareceu), modifique a variável volátil através do método TryReleashareded e TryAcquirarhared lê a variável volátil, que utiliza as regras voláteis;
Isso aproveita as regras do pedido do programa, definindo uma variável de resultado não volátil antes do tryreleaseshareded e depois lendo a variável de resultado após o TryAcquireshared.
Isso garante a visibilidade da variável de resultado. Semelhante ao nosso primeiro exemplo: usando regras de ordem do programa e regras voláteis para obter visibilidade variável normal.
O próprio Doug Lea disse que a tecnologia "uso da ajuda" é muito propensa a erros e deve ser usada com cautela. Mas, em alguns casos, esse tipo de "alavancagem" é muito razoável.
De fato, o BlockingQueue também "usou" as regras que acontecem antes. Lembra da regra de desbloqueio? Quando o desbloqueio ocorre, os elementos internos devem ser visíveis.
Existem outras operações na biblioteca de classes que também "usam" o princípio que acontece antes: contêineres simultâneos, contagemwllatch, semáforo, futuro, executor, ciclicbarrier, trocador etc.
Em suma, em suma:
O princípio que acontece antes é o núcleo do JMM. Somente quando o princípio da HB é atendido pode solicitar e a visibilidade será garantida, caso contrário, o compilador reordenará o código. O HB ainda define as regras para bloqueio e volátil.
Por combinação apropriada das regras da HB, o uso correto de variáveis compartilhadas comuns pode ser alcançado.
Ok, o acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.