Variável volátil em Java

Então, estou lendo este livro intitulado Java Concurrency in Practice e estou preso a essa explicação que não consigo compreender sem um exemplo. Esta é a citação:

Quando o thread A grava em uma variável volátil e, subsequentemente, o thread B lê a mesma variável, os valores de todas as variables ​​que estavam visíveis para A antes de gravar na variável volátil tornam-se visíveis para B após a leitura da variável volátil.

Alguém pode me dar um contra-exemplo de porque “os valores de todas as variables ​​que eram visíveis para A antes de escrever para a variável volátil tornam-se visíveis para B após a leitura da variável volátil”?

Estou confuso porque todas as outras variables ​​não voláteis não se tornam visíveis para B antes de ler a variável volátil?

O encadeamento B pode ter um cache local da CPU dessas variables. Uma leitura de uma variável volátil garante que qualquer cache intermediário liberado de uma gravação anterior para o volátil seja observado.

Por exemplo, leia o link a seguir, que conclui com “Corrigindo Bloqueio com Verificação Dupla usando Volátil”:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Declarar uma variável Java volátil significa:

  • O valor dessa variável nunca será armazenado em cache localmente: todas as leituras e gravações irão diretamente para a “memory principal”.
  • O access à variável atua como se estivesse dentro de um bloco sincronizado, sincronizado em si mesmo.

Apenas para sua referência, quando é necessário a volatilidade?

Quando vários encadeamentos usam a mesma variável, cada encadeamento terá sua própria cópia do cache local para essa variável. Então, quando está atualizando o valor, ele é realmente atualizado no cache local, não na memory da variável principal. O outro segmento que está usando a mesma variável não sabe nada sobre os valores alterados pelo outro segmento. Para evitar esse problema, se você declarar uma variável como volátil, ela não será armazenada no cache local. Sempre que o thread estiver atualizando os valores, ele será atualizado para a memory principal. Assim, outros segmentos podem acessar o valor atualizado.

De JLS §17.4.7 execuções bem- formadas

Consideramos apenas execuções bem formadas. Uma execução E = é bem formada se as seguintes condições forem verdadeiras:

  1. Cada leitura vê uma gravação para a mesma variável na execução. Todas as leituras e gravações de variables ​​voláteis são ações voláteis. Para todas as leituras r em A, temos W (r) em A e W (r) .v = rv A variável rv é volátil se e somente se r é uma leitura volátil, e a variável wv é volátil se e somente se w é uma gravação volátil.

  2. Acontece antes que o pedido seja um pedido parcial. Acontece antes que a ordem seja dada pelo fechamento transitivo de sincronizações – com bordas e ordem de programa. Deve ser uma ordem parcial válida: reflexiva, transitiva e antisimétrica.

  3. A execução obedece à consistência intra-thread. Para cada encadeamento t, as ações executadas por t em A são as mesmas que seriam geradas por esse encadeamento em ordem de programa em isolamento, com cada escrita gravando o valor V (w), dado que cada leitura r vê o valor V ( W (r)). Os valores vistos por cada leitura são determinados pelo modelo de memory. A ordem de programa dada deve refletir a ordem do programa em que as ações seriam realizadas de acordo com a semântica intra-thread de P.

  4. A execução é realizada antes de ser consistente (§17.4.6).

  5. A execução obedece à consistência de ordem de synchronization. Para todas as leituras voláteis r em A, não é assim que (r, W (r)) ou que existe uma vitória escrita A tal que wv = rv e assim (W (r), w) e assim ( w, r).

Link Útil: O que realmente sabemos sobre simultaneidade sem bloqueio em Java?

Se uma variável não for volátil, o compilador e a CPU poderão reordenar as instruções livremente, conforme necessário, para otimizar o desempenho.

Se a variável agora for declarada volátil, o compilador não tentará otimizar os accesss (leituras e gravações) para essa variável. No entanto, pode continuar a otimizar o access para outras variables.

No tempo de execução, quando uma variável volátil é acessada, a JVM gera instruções de barreira de memory apropriadas para a CPU. A barreira de memory atende ao mesmo propósito – a CPU também evita instruções de reordenamento.

Quando uma variável volátil é gravada em (pelo thread A), todas as gravações para qualquer outra variável são preenchidas (ou pelo menos parecerão ser) e tornadas visíveis para A antes da gravação na variável volátil; isso é geralmente devido a uma instrução de barreira de gravação de memory. Da mesma forma, qualquer leitura de outras variables ​​será concluída (ou parecerá ser) antes da leitura (pelo encadeamento B); isso é geralmente devido a uma instrução de barreira de leitura de memory. Esta ordenação de instruções que é reforçada pela (s) barreira (s), significará que todas as gravações visíveis para A serão visíveis B. Isto, no entanto, não significa que qualquer reordenamento de instruções não tenha acontecido (o compilador pode ter executado reordenar para outras instruções); Significa simplesmente que, se quaisquer gravações visíveis a A ocorressem, elas seriam visíveis para B. Em termos mais simples, isso significa que a ordem estrita do programa não é mantida.

Vou apontar para este writeup sobre Barreiras de Memória e concurrency de JVM , se você quiser entender como a JVM emite instruções de barreira de memory, com mais detalhes.

Perguntas relacionadas

  1. O que é uma cerca de memory?
  2. Quais são alguns truques que um processador faz para otimizar o código?

Threads podem armazenar em cache valores de variables ​​que outros threads podem ter atualizado desde que foram lidos. A palavra-chave volatile força todos os encadeamentos a não armazenar valores em cache.

Este é simplesmente um bônus adicional que o modelo de memory oferece, se você trabalha com variables ​​voláteis.

Normalmente (ou seja, na ausência de variables ​​voláteis e synchronization), a VM pode tornar as variables ​​de um thread visíveis para outros threads na ordem que desejar, ou não de todo. Por exemplo, o encadeamento de leitura poderia ler alguma mistura de versões anteriores de outras atribuições de variables ​​de encadeamentos. Isso é causado pelo fato de os encadeamentos serem executados em diferentes CPUs com seus próprios caches, que às vezes só são copiados para a “memory principal” e, adicionalmente, por reordenamento de código para fins de otimização.

Se você usou uma variável volátil, assim que o thread B ler algum valor X, a VM garante que qualquer coisa que o thread A tenha escrito antes de escrever X também é visível para B. (E também tudo o que A garantiu como visível) , transitivamente).

Garantias semelhantes são dadas para blocos sincronizados e outros tipos de bloqueios.