A variável volátil fornece visibilidade do encadeamento e não garante segurança e atomicidade do encadeamento.
O que é visibilidade do tópico:
Os bloqueios fornecem dois recursos principais: exclusão e visibilidade mútuas. Exclusão mútua significa que apenas um thread pode manter um bloqueio específico por vez, para que esse recurso possa ser usado para implementar um protocolo de acesso coordenado para dados compartilhados, para que apenas um thread possa usar os dados compartilhados por vez. A visibilidade é um pouco mais complexa e deve garantir que as alterações nos dados compartilhados antes de liberar o bloqueio sejam visíveis para outro tópico que adquira posteriormente o bloqueio - sem essa garantia de visibilidade fornecida pelo mecanismo de sincronização, as variáveis compartilhadas observadas pelo encadeamento podem ser pré -modificadas ou valores inconsistentes, o que causará muitos problemas sérios.
Veja a semântica do volátil :
O volátil é equivalente a uma fraca implementação de sincronizado, o que significa que implementa a semântica sincronizada volátil, mas não possui um mecanismo de bloqueio. Ele garante que as atualizações no campo volátil informem outros threads de maneira previsível.
O Volatile contém a seguinte semântica:
(1) O modelo de armazenamento Java não reordenará as operações de instruções valáteis: isso garante que as operações em variáveis voláteis sejam executadas na ordem em que as instruções aparecem.
(2) A variável volátil não será armazenada em cache no registro (apenas os threads são visíveis) ou outros locais que não são visíveis para a CPU. O resultado da variável volátil é sempre lido da memória principal sempre. Em outras palavras, para a modificação da variável volátil, outros threads são sempre visíveis e não estão usando variáveis dentro da pilha de threads. Ou seja, na lei, depois de escrever uma variável valátil, qualquer operação de leitura subsequente pode ser entendida para ver o resultado desta operação de gravação.
Embora as características da variável volátil sejam boas, o volátil não pode garantir a segurança do encadeamento. Ou seja, a operação do campo volátil não é atômica. A variável volátil só pode garantir a visibilidade (outros threads podem entender os resultados depois de ver essa alteração após a modificação de um thread). Para garantir atomicidade, você só pode travá -lo até agora!
Princípios para o uso de voláteis:
Três princípios para aplicar variáveis voláteis:
(1) As variáveis de gravação não dependem do valor dessa variável, ou apenas um encadeamento modifica essa variável
(2) O estado da variável não precisa participar das restrições invariantes com outras variáveis
(3) As variáveis de acesso não precisam ser bloqueadas
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 a 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 três 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.
Use volátil corretamente:
Modo #1: sinalizadores de status
Talvez a especificação da implementação da variável volátil seja simplesmente usar um sinalizador de status booleano para indicar que ocorreu um importante evento único, como concluir a inicialização ou solicitar o tempo de inatividade.
Muitas aplicações incluem uma estrutura de controle na forma de "executar algum trabalho quando o programa não estiver pronto para parar", como mostra a Listagem 2:
Listagem 2. Use a variável volátil como um sinalizador de status
Deslocado booleano volátil requestado; … Public void Shutdown () {ShutdownRequested = true; } public void Dowork () {while (! ShutdownRequested) {// Do Stuff}}É muito provável que o método Shutdown () seja chamado de fora do loop - isto é, em outro thread - para que algum tipo de sincronização precisa ser executado para garantir que a visibilidade da variável de desligamento seja implementada corretamente. (Pode ser chamado do ouvinte JMX, o ouvinte da Operação no Evento da GUI, através do RMI, através de um serviço da Web, etc.). No entanto, escrever um loop com um bloco sincronizado é muito mais problemático do que escrever com o sinalizador de status volátil mostrado na Listagem 2. Como o volátil simplifica a codificação e os sinalizadores de status não dependem de nenhum outro estado do programa, é muito adequado para o volátil aqui.
Uma característica comum desse tipo de tag de estado é que geralmente há apenas uma transição de estado; A bandeira de Shutdown -Requested é convertida de falsa para verdadeira, e o programa para. Esse padrão pode ser estendido ao sinalizador de status da transição para frente e para trás, mas só pode ser estendido se o período de transição não for notado (do falso para o verdadeiro e do falso). Além disso, são necessários alguns mecanismos de conversão de estado atômico, como variáveis atômicas.
Modo #2: Publicação segura única
A falta de sincronização pode levar à visibilidade inatingível, o que torna mais difícil determinar quando escrever uma referência de objeto em vez de um valor primitivo. Na ausência de sincronização, um valor atualizado referenciado por um objeto (escrito por outro thread) pode ser encontrado e o valor antigo do estado desse objeto existe simultaneamente. (Esta é a causa raiz do famoso problema de travamento verificado duas vezes, onde as referências de objeto são lidas sem sincronização, resultando no problema que você pode ver uma referência atualizada, mas ainda ver um objeto incompletamente construído por meio dessa referência).
Uma técnica para implementar a publicação segura de objetos é definir referências de objetos como tipo volátil. A Listagem 3 mostra um exemplo em que o encadeamento em segundo plano carrega alguns dados do banco de dados durante a inicialização. Outros códigos, quando conseguem utilizar esses dados, verifique se eles foram publicados antes do uso.
Listagem 3. Usando a variável volátil para uma versão segura única
classe pública BackgroundFloobleLoader {public volátil fLOOBLE THEFLOOBLE; public void initinbackground () {// faz muitas coisas theflooble = new floole (); // Esta é a única escrita para theflooble}} classe pública, algum teto {public void Dowork () {while (true) {// Faça algumas coisas ... // use o flutuar, mas somente se estiver pronto se (floobleloader.theflooble! }}}Se a referência flooble não for do tipo volátil, o código em Dowork () receberá um fLole incompletamente construído quando não referenciam o flooble.
Uma condição necessária para esse padrão é que o objeto publicado deve ser seguro para threads ou um objeto imutável válido (imutável eficaz significa que o estado do objeto nunca será modificado após a publicação). As referências do tipo volátil garantem a visibilidade do formulário de publicação do objeto, mas a sincronização adicional é necessária se o estado do objeto mudar após a publicação.
Padrão #3: observação independente
Outro modo simples de usar o volátil com segurança é "liberar" observações regularmente para o uso interno do programa. Por exemplo, suponha que haja um sensor ambiente capaz de detectar a temperatura ambiente. Um encadeamento de segundo plano pode ler o sensor a cada poucos segundos e atualizar a variável volátil que contém o documento atual. Em seguida, outros threads podem ler essa variável para que possam ver o valor mais recente da temperatura a qualquer momento.
Outro aplicativo que usa esse modo é coletar as estatísticas do programa. A Listagem 4 mostra como o mecanismo de autenticação se lembra do nome do usuário que foi conectado pela última vez. Repita a referência do LastUser para publicar valores para outras partes do programa.
Listagem 4. Usando a variável volátil para publicar várias observações independentes
classe pública UserManager {public volatile string lastUser; public boolean autenticate (string user, string senha) {boolean válido = senhaDisValid (usuário, senha); if (válido) {user u = new user (); ActiveUsers.add (u); lastUser = usuário; } retornar válido; }}Este modo é uma extensão do modo anterior; Publicando um certo valor para uso em outras partes do programa, mas, diferentemente da publicação de um evento único, é uma série de eventos independentes. Esse padrão exige que o valor publicado seja válido e imutável - ou seja, o status do valor não será alterado após a publicação. O código que usa esse valor precisa ser claro que o valor pode mudar a qualquer momento.
Modo #4: Modo "Bean Volatile"
O padrão de feijão volátil é adequado para estruturas que usam Javabeans como uma "estrutura de honra". No padrão volátil do feijão, os Javabeanos são usados como um conjunto de contêineres com propriedades independentes dos métodos Getter e/ou Setter. O princípio básico do padrão volátil do feijão é que muitas estruturas fornecem contêineres para detentores de dados voláteis (como httpssession), mas os objetos colocados nesses contêineres devem ser seguros.
No modo de feijão volátil, todos os membros de dados de um Javabean são do tipo volátil, e os métodos Getter e Setter devem ser muito comuns - eles não podem conter nenhuma lógica, exceto para obter ou definir propriedades correspondentes. Além disso, para os membros de dados mencionados pelo objeto, o objeto referenciado deve ser válido e imutável. (Isso proibirá propriedades com valores de matriz, porque quando uma referência de matriz é declarada como volátil, apenas a referência e não a própria matriz têm semântica volátil). Para qualquer variável volátil, invariantes ou restrições não podem conter propriedades javabeus. Os exemplos da Listagem 5 mostram Javabeans que aderem ao padrão volátil do feijão:
Modo #4: Modo "Bean Volatile"
@ThreadSafe public class Pessoa {String volátil privada FirstName; string volátil privada LastName; private INT AGE INT; public String getFirstName () {return FirstName; } public string getLastName () {return lastName; } public int getage () {Age de retorno; } public void setFirstName (String FirstName) {this.firstName = FirstName; } public void setLastName (string lastName) {this.LastName = lastName; } public void setage (int Age) {this.age = Age; }}Modo avançado de volátil
Os padrões descritos nas seções anteriores cobrem a maioria dos casos básicos de uso e o uso volátil nesses padrões é muito útil e simples. Esta seção apresenta um modo mais avançado no qual a Volátil fornecerá vantagens de desempenho ou escalabilidade.
Os modos avançados de aplicações voláteis são muito frágeis. Portanto, as suposições devem ser cuidadosamente comprovadas, e esses padrões são estritamente encapsulados, porque mesmo alterações muito pequenas podem corromper seu código! Da mesma forma, o motivo do uso de um caso de uso volátil mais avançado é que ele pode melhorar o desempenho, garantindo que você realmente determine que precisa alcançar esse benefício de desempenho antes de começar a aplicar os padrões avançados. Existem trocas nesses padrões, desistindo da legibilidade ou manutenção em troca de possíveis ganhos de desempenho-se você não precisar de melhorias de desempenho (ou não pode provar que precisa dele através de um programa de teste rigoroso), é provável que seja um mau negócio, porque você provavelmente perderá menos dinheiro e obter algo menos do que o que você desiste.
Modo #5: Ler-Write Lock Strategy com baixa sobrecarga
Até agora, você deve entender que o volátil não é capaz o suficiente para implementar contadores. Como ++ X é na verdade uma combinação simples de três operações (leia, adicione, armazene), se vários threads tentarem executar operações incrementais no contador volátil ao mesmo tempo, seu valor atualizado pode ser perdido.
No entanto, se a operação de leitura for muito mais que a operação de gravação, você poderá usar uma bloqueio interno e variáveis voláteis para reduzir a sobrecarga do caminho do código público. Os contadores seguros de encadeamento mostrados na Listagem 6 usam sincronizados para garantir que as operações incrementais sejam atômicas e voláteis para garantir a visibilidade do resultado atual. Esse método pode alcançar um melhor desempenho se as atualizações não forem frequentes, porque a sobrecarga dos caminhos de leitura envolve apenas a operação de leitura volátil, que geralmente é melhor que a sobrecarga de uma aquisição de bloqueio sem competição.
Listagem 6. Use volátil e sincronizado para obter "bloqueios mais baixos de leitura de leitura"
@ThreadSafe Public Class Cheesycounter {// Emprega o truque de trava de leitura-write barato // Todas as operações mutárias devem ser feitas com o valor 'este' bloqueado @guardedby ("this") private volátil int valor; public int getValue () {return value; } public sincronizado int increment () {retorno valor ++; }}A razão pela qual essa técnica é chamada de "trava de leitura aérea inferior" é porque você usa um mecanismo de sincronização diferente para operações de leitura de leitura. Como a operação de gravação neste exemplo viola a primeira condição de usar o volátil, o contador não pode ser implementado com segurança com o volátil - você deve usar um bloqueio. No entanto, você pode usar operações voláteis em leitura para garantir a visibilidade do valor atual, para que você possa usar bloqueios para executar todas as alterações e somente leitura com volátil. Entre eles, o bloqueio permite que apenas um thread acesse o valor por vez, e o volátil permite que vários threads executem operações de leitura. Portanto, ao usar o Volátil para garantir que o caminho do código seja lido, é mais compartilhado do que usar o bloqueio para executar todos os caminhos de código - assim como uma operação de leitura e gravação. No entanto, lembre -se das fraquezas desse modelo em mente: se a aplicação mais básica desse modelo estiver além, ele se tornará muito difícil combinar esses dois mecanismos de sincronização concorrentes.
Sobre a reordenação de instruções e a regra que acontece antes
1. Deixe reordenar
A especificação do idioma Java estipula que o thread JVM mantém a semântica seqüencial internamente, ou seja, desde que o resultado final do programa seja equivalente a seus resultados em um ambiente seqüencial rigoroso, a ordem de execução das instruções pode ser inconsistente com a ordem do código. Este processo é reordenado por um comando. O significado da reordenação de instruções é que a JVM pode reordenar adequadamente as instruções da máquina de acordo com as características do processador (sistema de cache de vários níveis da CPU, processador multi-core etc.), para que as instruções da máquina estejam mais alinhadas com as características de execução da CPU e maximizem o desempenho da máquina.
O modelo mais simples para execução do programa é executar na ordem em que as instruções aparecem, independentemente da CPU que executa as instruções, garantindo a portabilidade das instruções na maior extensão. O termo profissional para este modelo é chamado de modelo de consistência seqüencial. No entanto, os sistemas modernos de computadores e arquiteturas de processador não garantem isso (porque a designação artificial nem sempre pode garantir a conformidade com as características de processamento da CPU).
2. Appens-se antes da regra
O modelo de armazenamento Java tem um princípio antes, ou seja, se a ação B deseja ver o resultado da execução da ação a (independentemente de a/b ser executada no mesmo thread), a/b precisa satisfazer o relacionamento acontecer antes.
Antes de apresentar a regra que acontece antes, introduza um conceito: JMM Action (Java Memeory Model Action), o Java armazena ações modelo. Uma ação inclui: Leia e gravar variáveis, monitorar bloqueio e liberação de bloqueios, thread start () e junção (). A fechadura será mencionada mais tarde.
acontece antes das regras completas:
(1) Cada ação no mesmo encadeamento acontece antes de qualquer ação que apareça depois dele.
(2) O desbloqueio de um monitor acontece antes de cada bloqueio subsequente no mesmo monitor.
(3) Escreva a operação no campo volátil acontece antes de cada operação de leitura subsequente do mesmo campo.
(4) A chamada para Thread.start () acontecerá antes das ações no thread de inicialização.
(5) Todas as ações no encadeamento acontecem antes de verificar outros threads para encerrar este thread ou retornar no thread.join () ou thread.isalive () == false.
(6) Um thread A chama a interrupção () de outro encadeamento B acontece antes de o thread A descobre que B é interrompido por A (b lança uma exceção ou um detecta B é interrompido () ou interrompido ()).
(7) O fim de um construtor de objeto acontece antes e o início do finalizador do objeto
(8) Se uma ação acontecer antes está em ação B, e B ação acontece antes e C Ação, então uma ação acontece antes de estar em ação C.
O acima é tudo sobre este artigo. Vou apresentá -lo a você aqui. Espero que seja útil aprender e entender variáveis voláteis em Java.