A variável volátil no idioma Java pode ser considerada como uma "sincronizada mais leve"; Comparado com o bloco sincronizado, a variável volátil requer menor codificação e sobrecarga de tempo de execução, mas a funcionalidade que ele pode alcançar é apenas parte do sincronizado.
Trancar
Os bloqueios fornecem dois recursos principais: exclusão e visibilidade mútuas.
Variáveis voláteis
A variável volátil tem as características de visibilidade de sincronizadas, mas não possui as características atômicas. Isso significa que o encadeamento pode descobrir automaticamente o valor mais recente da variável volátil.
Variáveis voláteis podem ser usadas para fornecer segurança do encadeamento, mas só podem ser aplicadas a um conjunto muito limitado de casos de uso: não há restrição entre várias variáveis ou entre o valor atual de uma variável e o valor modificado. Portanto, o uso apenas de volátil não é suficiente para implementar contadores, mutexes ou qualquer classe com invariantes associados a várias variáveis (por exemplo, "iniciar <= end").
Para simplicidade ou escalabilidade, você pode usar variáveis voláteis em vez de bloqueios. Alguns idiomas são mais fáceis de codificar e ler ao usar variáveis voláteis em vez de bloqueios. Além disso, a variável volátil não causa bloqueio de roscas como uma trava e, portanto, raramente causa problemas de escalabilidade. Em alguns casos, a variável volátil também pode fornecer vantagens de desempenho sobre os bloqueios se a operação de leitura for muito maior que a operação de gravação.
Condições para o uso correto de variáveis voláteis
Você só pode usar a variável volátil em vez de bloqueios em casos limitados. Para tornar a variável volátil segurança ideal, as duas condições a seguir devem ser atendidas ao mesmo tempo:
As operações de gravação para variáveis não dependem do valor atual.
Essa variável não está incluída no invariante com outras variáveis.
De fato, essas condições indicam que esses valores válidos que podem ser gravados na variável volátil são independentes do estado de qualquer programa, incluindo o estado atual da variável.
Os limites da primeira condição impedem que a variável volátil seja usada como um contador seguro para roscas. Embora uma operação incremental (x ++) pareça uma operação separada, na verdade é uma operação combinada composta por uma sequência de operações de leitura-modificação-gravação que deve ser realizada atomicamente e a volátil não pode fornecer as propriedades atômicas necessárias. A implementação da operação correta requer manter o valor de x constante durante a operação, o que não é possível com a variável volátil. (No entanto, se o valor for ajustado para ser escrito apenas de um único thread, a primeira condição poderá ser ignorada.)
A maioria das situações de programação entra em conflito com uma dessas duas condições, tornando a variável volátil não tão universalmente aplicável à segurança do encadeamento como sincronizado. A Listagem 1 mostra uma classe de alcance numérico não-segura. Ele contém um invariante - o limite inferior é sempre menor ou igual ao limite superior.
Dê um exemplo
Vamos ver um exemplo abaixo. Implementamos um contador. Cada vez que o thread inicia, o método Counter Inc será chamado para adicionar o contador ao ambiente de execução - versão JDK: JDK1.6.0_31, Memória: 3G CPU: x86 2.4g
public classe contador {public static int count = 0; public static void Inc () {// O atraso aqui é 1 milissegundo, tornando o resultado óbvio tentativa {thread.sleep (1); } catch (interruptedException e) {} count ++; } public static void main (string [] args) {// inicia 1000 threads ao mesmo tempo para executar cálculos i ++ e ver o resultado real para (int i = 0; i <1000; i ++) {new Thread (new Runnable () {@Override public void Run () {Counter.inc ();}}). } // O valor de cada execução aqui pode ser diferente, talvez 1000 System.out.println ("RUNIDO RUNHE: CONTA.COUNT =" + contador.Count); }} Resultado em execução: contador.count = 995
O resultado da operação real pode ser diferente a cada vez. O resultado da máquina é: resultado em execução: contador.count = 995. Pode-se observar que, em um ambiente multithread, o Count não espera que o resultado seja 1000.
Muitas pessoas pensam que este é um problema de simultaneidade com vários threades. Você só precisa adicionar volátil antes da contagem de variáveis para evitar esse problema. Em seguida, estamos modificando o código para ver se o resultado atende às nossas expectativas.
public classe contador {public volatile static int count = 0; public static void Inc () {// O atraso aqui é 1 milissegundo, tornando o resultado óbvio tentativa {thread.sleep (1); } catch (interruptedException e) {} count ++; } public static void main (string [] args) {// inicia 1000 threads ao mesmo tempo, execute cálculos I ++ e veja o resultado real para (int i = 0; i <1000; i ++) {new Thread (new Runnable () {@Override public void () {counter.inc ();}}). } // O valor de cada execução aqui pode ser diferente, possivelmente 1000 System.out.println ("RUNIDO RUNHO: contador.Count =" + contador.count); }}Resultado em execução: contador.count = 992
O resultado da operação ainda não é tão 1000 quanto esperávamos. Vamos analisar os motivos abaixo
No artigo de coleta de lixo Java, a alocação de memória no momento da JVM é descrita. Uma das áreas de memória é a pilha de máquina virtual da JVM. Cada encadeamento possui uma pilha de thread quando é executada e a pilha de thread salva as informações de valor variável durante as execuções do thread. Quando um thread acessa o valor de um determinado objeto, primeiro encontra o valor da variável correspondente à memória da heap através da referência do objeto e, em seguida, carrega o valor específico da variável de memória da heap na memória local do encadeamento para criar uma cópia variável. Depois disso, o thread não tem mais nenhum relacionamento com o valor da variável de memória de heap do objeto, mas modifica diretamente o valor da variável de cópia e, em um certo momento após a modificação (antes da saída do encadeamento), ele grava automaticamente o valor da variável de encadeamento de volta à variável Heap do objeto. Dessa forma, o valor do objeto na pilha mudará. A imagem a seguir descreve a interação desta escrita
EAD e Carregar variáveis de cópia da memória principal para a memória de trabalho atual
Use e atribua código de execução para alterar o valor variável compartilhado
Armazene e escreva o conteúdo da memória principal relacionada à memória com dados de memória de trabalho
onde usar e atribuir podem aparecer várias vezes
No entanto, essas operações não são atômicas, ou seja, após a carga de leitura, se a variável de contagem de memória principal for modificada, o valor na memória de trabalho do encadeamento não causará alterações correspondentes, pois foi carregada, portanto o resultado calculado será diferente do esperado.
Para variáveis modificadas por voláte
Por exemplo, se o thread 1 e o thread 2 estiverem executando operações de leitura e carga e descobrir que o valor da contagem na memória principal é 5, o valor mais recente será carregado
Depois que a contagem de heap for modificada no encadeamento 1, ela será gravada na memória principal e a variável de contagem na memória principal se tornará 6.
Como o Thread 2 já realizou a operação de leitura e carga, o valor variável da contagem principal de memória também será atualizado para 6 após a operação.
Isso faz com que a concorrência ocorra após a modificação de dois threads com a palavra -chave volátil no tempo.