Volátil Vs Atomic

Eu li em algum lugar abaixo da linha.

Java palavra-chave volátil não significa atômica, seu equívoco comum que depois de declarar volátil, ++ operação será atômica, para tornar a operação atômica você ainda precisa garantir o access exclusivo usando o método synchronized ou bloquear em Java.

Então, o que acontecerá se dois threads atacarem uma variável primitiva volatile ao mesmo tempo?

Isso significa que quem quer que se feche, isso estará definindo seu valor primeiro. E se, enquanto isso, algum outro segmento aparecer e ler o valor antigo enquanto o primeiro thread estiver alterando seu valor, o novo thread não lerá seu valor antigo?

Qual é a diferença entre palavra-chave atômica e volátil?

O efeito da palavra-chave volatile é aproximadamente que cada operação de leitura ou gravação individual nessa variável é atômica.

Notavelmente, no entanto, uma operação que requer mais de uma leitura / gravação – como i++ , que é equivalente a i = i + 1 , que lê e escreve – não é atômica, já que outro thread pode gravar em i entre a leitura e a escrita.

As classs Atomic , como AtomicInteger e AtomicReference , fornecem uma ampla variedade de operações atomicamente, especificamente incluindo incremento para AtomicInteger .

Volátil e Atômica são dois conceitos diferentes. Volatile garante que um certo estado esperado (memory) seja verdadeiro em diferentes threads, enquanto o Atomics garante que a operação em variables ​​seja executada atomicamente.

Tome o seguinte exemplo de dois segmentos em Java:

Tópico A:

 value = 1; done = true; 

Tópico B:

 if (done) System.out.println(value); 

Começando com value = 0 e done = false a regra de encadeamento nos diz que ela é indefinida, quer a linha B imprima ou não o valor. Além disso, o valor é indefinido nesse ponto também! Para explicar isso, você precisa saber um pouco sobre o gerenciamento de memory Java (que pode ser complexo), resumindo: Threads podem criar cópias locais de variables, e a JVM pode reordenar código para otimizá-lo, portanto não há garantia de que o código acima é executado exatamente nessa ordem. A configuração feita como true e o valor de configuração como 1 pode ser um possível resultado das otimizações do JIT.

volatile apenas garante que, no momento do access a essa variável, o novo valor será imediatamente visível para todos os outros threads e a ordem de execução garante que o código esteja no estado que você esperaria que fosse. Portanto, no caso do código acima, a definição done como volátil garantirá que, sempre que o thread B verificar a variável, ela seja falsa ou verdadeira e, se for verdadeira, o value também será definido como 1.

Como um efeito colateral do volátil , o valor de tal variável é definido em todo o segmento atomicamente (com um custo muito menor de velocidade de execução). No entanto, isso é importante apenas em sistemas de 32 bits que usam variables ​​longas (de 64 bits) (ou similares), na maioria dos casos, a configuração / leitura de uma variável é atômica. Mas há uma diferença importante entre um access atômico e uma operação atômica. Volátil apenas garante que o access seja atomicamente, enquanto o Atomics garante que a operação seja atomicamente.

Tome o seguinte exemplo:

 i = i + 1; 

Não importa como você define i, um Thread diferente lendo o valor apenas quando a linha acima é executada pode obter i, ou i + 1, porque a operação não é atomicamente. Se o outro thread definir i para um valor diferente, no pior dos casos eu poderia ser definido de volta para o que era antes pelo thread A, porque estava apenas no meio do cálculo de i + 1 com base no valor antigo e, em seguida, defina i novamente para esse valor antigo + 1. Explicação:

 Assume i = 0 Thread A reads i, calculates i+1, which is 1 Thread B sets i to 1000 and returns Thread A now sets i to the result of the operation, which is i = 1 

Atomics como AtomicInteger garantem que tais operações ocorram atomicamente. Portanto, o problema acima não pode acontecer, eu seria ou 1000 ou 1001 uma vez que ambos os segmentos estão acabados.

Existem dois conceitos importantes no ambiente multithreading.

  1. atomicidade
  2. visibilidade

Volatile erradica o problema da visibilidade mas não trata a atomicidade. Volatile evitará que o compilador reordene a instrução que envolve a gravação e a leitura subseqüente de uma variável volátil. Por exemplo, k++ Aqui, k++ não é uma única instrução de máquina, mas sim três instruções de máquina.

  1. copie o valor para registrar
  2. incremente
  3. coloque de volta

Portanto, mesmo que você declare variável como volatile ela não tornará esta operação atômica, o que significa que outro segmento pode ver um resultado intermediário, que é um valor obsoleto ou indesejado para o outro segmento.

Mas AtomicInteger , AtomicReference são baseados na instrução Compare and swap . O CAS possui três operandos: um local de memory V no qual operar, o valor antigo esperado A e o novo valor B CAS atomicamente atualiza V para o novo valor B , mas somente se o valor em V corresponder ao valor antigo esperado A ; caso contrário, não faz nada. Em ambos os casos, ele retorna o valor atualmente em V Isso é usado pela JVM no AtomicInteger , AtomicReference e eles chamam a function como compareAndSet() . Se esta funcionalidade não for suportada pelo processador subjacente, a JVM implementa-a pelo bloqueio de rotação .

Tentar como indicado, o volatile lida apenas com a visibilidade.

Considere este snippet em um ambiente simultâneo:

 boolean isStopped = false; : : while (!isStopped) { // do some kind of work } 

A ideia aqui é que algum thread poderia alterar o valor de isStopped de false para true para indicar ao loop subseqüente que é hora de parar o loop.

Intuitivamente, não há problema. Logicamente, se outro segmento faz isStopped igual a true, o loop deve terminar. A realidade é que o loop provavelmente nunca terminará mesmo se outro thread fizer isStopped igual a true.

A razão para isso não é intuitiva, mas considere que os processadores modernos têm múltiplos núcleos e que cada núcleo possui múltiplos registros e múltiplos níveis de memory cache que não são acessíveis a outros processadores . Em outras palavras, valores que são armazenados em cache na memory local de um processador não são visíveis para threads executando em um processador diferente. Aqui reside um dos problemas centrais da concorrência: a visibilidade.

O Modelo de Memória Java não garante, de forma alguma, quando as alterações feitas em uma variável no encadeamento podem se tornar visíveis para outros encadeamentos. Para garantir que as atualizações sejam visíveis assim que forem feitas, você deve sincronizar.

A palavra-chave volatile é uma forma fraca de synchronization. Embora não faça nada por exclusão ou atomicidade mútua, ele fornece uma garantia de que as alterações feitas em uma variável em um thread se tornarão visíveis para outros threads assim que forem feitas. Como as leituras e gravações individuais para variables ​​que não são de 8 bytes são atômicas em Java, a declaração de variables volatile fornece um mecanismo fácil para fornecer visibilidade em situações em que não há outros requisitos de atomicidade ou exclusão mútua.

A palavra-chave volatile é usada:

  • para tornar operações não atômicas de 64 bits atômicas: long e double . (todos os outros accesss primitivos já estão garantidos para serem atômicos!)
  • fazer atualizações variables ​​garantidas para serem vistas por outros threads + efeitos de visibilidade: depois de escrever para uma variável volátil, todas as variables ​​visíveis antes de gravar essa variável se tornam visíveis para outro thread depois de ler a mesma variável volátil (acontecer antes da ordenação).

As classs java.util.concurrent.atomic.* São, de acordo com os documentos java :

Um pequeno kit de ferramentas de classs que suportam programação segura de thread sem trava em variables ​​únicas. Em essência, as classs neste pacote estendem a noção de valores voláteis, campos e elementos de matriz àqueles que também fornecem uma operação de atualização condicional atômica do formulário:

boolean compareAndSet(expectedValue, updateValue);

As classs atômicas são construídas em torno da function atômica compareAndSet(...) que mapeia para uma instrução de CPU atômica. As classs atômicas introduzem o ordenamento antes de acontecer como as variables volatile fazem. (com uma exceção: weakCompareAndSet(...) ).

Dos docs java:

Quando um thread vê uma atualização para uma variável atômica causada por um weakCompareAndSet, ele não necessariamente vê atualizações para quaisquer outras variables ​​que ocorreram antes do weakCompareAndSet.

Para sua pergunta:

Isso significa que quem quer que se feche, isso estará definindo seu valor primeiro. E se, enquanto isso, algum outro segmento aparecer e ler o valor antigo enquanto o primeiro thread estiver alterando seu valor, o novo thread não lerá seu valor antigo?

Você não bloqueia nada, o que você está descrevendo é uma condição de corrida típica que acontecerá eventualmente se os threads acessarem dados compartilhados sem a synchronization adequada. Como já foi mencionado, declarar uma variável volatile neste caso só irá garantir que outros threads verão a mudança da variável (o valor não será armazenado em cache em um registro de algum cache que é visto apenas por um thread).

Qual é a diferença entre o AtomicInteger e o volatile int ?

AtomicInteger fornece operações atômicas em um int com synchronization adequada (por exemplo, incrementAndGet() , getAndAdd(...) , …), volatile int apenas garantirá a visibilidade do int para outros threads.

Então, o que acontecerá se dois threads atacarem uma variável primitiva volátil ao mesmo tempo?

Geralmente cada um pode incrementar o valor. No entanto, em algum momento, ambos atualizarão o valor ao mesmo tempo e, em vez de incrementar pelo total de 2, o incremento de segmento por 1 e somente 1 será adicionado.

Isso significa que quem quer que se feche, isso estará definindo seu valor primeiro.

Não há bloqueio. Isso é o que é synchronized .

E se, enquanto isso, algum outro segmento aparecer e ler o valor antigo enquanto o primeiro thread estiver alterando seu valor, o novo thread não lerá seu valor antigo?

Sim,

Qual é a diferença entre palavra-chave atômica e volátil?

O AtomicXxxx envolve um volátil, então eles são basicamente iguais, a diferença é que ele fornece operações de nível mais alto, como o CompareAndSwap, que é usado para implementar o incremento.

O AtomicXxxx também suporta o lazySet. Isso é como um conjunto volátil, mas não interrompe o pipeline aguardando a conclusão da gravação. Pode significar que, se você ler um valor que acabou de escrever, poderá ver o valor antigo, mas não deve fazer isso de qualquer maneira. A diferença é que a configuração de um volátil leva cerca de 5 ns, o bit lazySet leva cerca de 0,5 ns.