Qual é a diferença entre atômica / volátil / sincronizada?

Como o trabalho atômico / volátil / sincronizado funciona internamente?

Qual é a diferença entre os seguintes blocos de código?

Código 1

private int counter; public int getNextUniqueIndex() { return counter++; } 

Código 2

 private AtomicInteger counter; public int getNextUniqueIndex() { return counter.getAndIncrement(); } 

Código 3

 private volatile int counter; public int getNextUniqueIndex() { return counter++; } 

O volatile funciona da seguinte maneira? É

 volatile int i = 0; void incIBy5() { i += 5; } 

equivalente a

 Integer i = 5; void incIBy5() { int temp; synchronized(i) { temp = i } synchronized(i) { i = temp + 5 } } 

Eu acho que dois threads não podem entrar em um bloco sincronizado ao mesmo tempo … estou certo? Se isso é verdade, então como o atomic.incrementAndGet() funciona sem synchronized ? E é seguro para threads?

E qual é a diferença entre leitura e escrita internas para variables ​​voláteis / variables ​​atômicas? Eu li em algum artigo que o segmento tem uma cópia local das variables ​​- o que é isso?

Você está perguntando especificamente sobre como eles funcionam internamente , então você está aqui:

Sem synchronization

 private int counter; public int getNextUniqueIndex() { return counter++; } 

Basicamente lê o valor da memory, incrementa-o e coloca de volta na memory. Isso funciona em thread único, mas hoje em dia, na era de caches multi-core, multi-CPU e multi-level, ele não funciona corretamente. Em primeiro lugar, introduz a condição de corrida (vários segmentos podem ler o valor ao mesmo tempo), mas também problemas de visibilidade. O valor pode ser armazenado apenas na memory da CPU ” local ” (algum cache) e não ser visível para outras CPUs / núcleos (e, portanto, – encadeamentos). É por isso que muitos se referem à cópia local de uma variável em um segmento. É muito inseguro. Considere este código popular mas interrompido de interrupção de thread:

 private boolean stopped; public void run() { while(!stopped) { //do some work } } public void pleaseStop() { stopped = true; } 

Adicione volatile à variável de stopped e ele funciona bem – se qualquer outro segmento modifica a variável stopped via método pleaseStop() , você está garantido para ver essa mudança imediatamente no loop while(!stopped) do thread de trabalho. BTW isso não é uma boa maneira de interromper um thread, consulte: Como parar um thread que está em execução para sempre sem qualquer uso e Parar um segmento java específico .

AtomicInteger

 private AtomicInteger counter = new AtomicInteger(); public int getNextUniqueIndex() { return counter.getAndIncrement(); } 

A class AtomicInteger usa operações de CPU de nível baixo CAS ( compare e troca ) (não é necessária synchronization!) Elas permitem que você modifique uma variável específica somente se o valor presente for igual a outra coisa (e é retornado com êxito). Então, quando você executa getAndIncrement() ele é executado em um loop (implementação real simplificada):

 int current; do { current = get(); } while(!compareAndSet(current, current + 1)); 

Então basicamente: leia; tente armazenar o valor incrementado; se não for bem-sucedido (o valor não é mais igual a current ), leia e tente novamente. O compareAndSet() é implementado no código nativo (assembly).

volatile sem synchronization

 private volatile int counter; public int getNextUniqueIndex() { return counter++; } 

Este código não está correto. Ele corrige o problema de visibilidade (o volatile garante que outros segmentos possam ver as alterações feitas no counter ), mas ainda tem uma condição de corrida. Isso foi explicado várias vezes: pré / pós-incremento não é atômico.

O único efeito colateral do volatile é o ” flushing ” dos caches, de modo que todos os outros participantes vejam a versão mais recente dos dados. Isso é muito rigoroso na maioria das situações; é por isso que volatile não é padrão.

volatile sem synchronization (2)

 volatile int i = 0; void incIBy5() { i += 5; } 

O mesmo problema acima, mas ainda pior porque não é private . A condição de corrida ainda está presente. Por que isso é um problema? Se, digamos, dois threads executarem esse código simultaneamente, a saída poderá ser + 5 ou + 10 . No entanto, você tem a garantia de ver a alteração.

Múltipla independente synchronized

 void incIBy5() { int temp; synchronized(i) { temp = i } synchronized(i) { i = temp + 5 } } 

Surpresa, este código está incorreto também. Na verdade, isso é completamente errado. Primeiro de tudo você está sincronizando em i , que está prestes a ser alterado (além disso, i é um primitivo, então eu acho que você está sincronizando em um Integer temporário criado via autoboxing …) Completamente falho. Você também pode escrever:

 synchronized(new Object()) { //thread-safe, SRSLy? } 

Não há dois segmentos podem entrar no mesmo bloco synchronized com o mesmo bloqueio . Neste caso (e similarmente em seu código) o object de bloqueio muda a cada execução, portanto, efetivamente synchronized não tem efeito.

Mesmo se você usou uma variável final (ou this ) para synchronization, o código ainda está incorreto. Dois threads podem primeiro ler i para temp síncrona (tendo o mesmo valor localmente em temp ), então o primeiro designa um novo valor para i (digamos, de 1 a 6) e o outro faz a mesma coisa (de 1 a 6) .

A synchronization deve abranger desde a leitura até a atribuição de um valor. Sua primeira synchronization não tem efeito (ler um int é atômico) e o segundo também. Na minha opinião, estas são as formas corretas:

 void synchronized incIBy5() { i += 5 } void incIBy5() { synchronized(this) { i += 5 } } void incIBy5() { synchronized(this) { int temp = i; i = temp + 5; } } 

Declarar uma variável como volátil significa que modificar seu valor afeta imediatamente o armazenamento de memory real da variável. O compilador não pode otimizar quaisquer referências feitas à variável. Isso garante que quando um thread modifica a variável, todos os outros threads verão o novo valor imediatamente. (Isso não é garantido para variables ​​não voláteis.)

A declaração de uma variável atômica garante que as operações feitas na variável ocorram de forma atômica, ou seja, que todas as sub-etapas da operação sejam concluídas dentro do encadeamento em que são executadas e não são interrompidas por outros encadeamentos. Por exemplo, uma operação de incremento e teste requer que a variável seja incrementada e, em seguida, comparada com outro valor; uma operação atômica garante que ambas as etapas serão concluídas como se fossem uma única operação indivisível / ininterrupta.

A synchronization de todos os accesss a uma variável permite que apenas um único encadeamento por vez acesse a variável e força todos os outros encadeamentos a aguardar que esse encadeamento de access libere seu access à variável.

O access sincronizado é semelhante ao access atômico, mas as operações atômicas são geralmente implementadas em um nível mais baixo de programação. Além disso, é perfeitamente possível sincronizar apenas alguns accesss a uma variável e permitir que outros accesss não sejam sincronizados (por exemplo, sincronizar todas as gravações com uma variável, mas nenhuma das leituras a partir dela).

Atomicidade, synchronization e volatilidade são atributos independentes, mas são normalmente usados ​​em combinação para impor a cooperação apropriada do encadeamento para acessar variables.

Adenda (abril de 2016)

O access sincronizado a uma variável geralmente é implementado usando um monitor ou semáforo . Esses são mecanismos de exclusão mútua ( mutex exclusion) de baixo nível que permitem que um thread adquira controle de uma variável ou bloco de código exclusivamente, forçando todos os outros threads a aguardarem se eles também tentarem adquirir o mesmo mutex. Uma vez que o thread proprietário libera o mutex, outro thread pode adquirir o mutex por vez.

Adenda (julho de 2016)

A synchronization ocorre em um object . Isso significa que chamar um método sincronizado de uma class bloqueará this object da chamada. Os methods sincronizados estáticos bloquearão o próprio object Class .

Da mesma forma, inserir um bloco sincronizado requer o bloqueio this object do método.

Isso significa que um método (ou bloco) sincronizado pode estar em execução em vários encadeamentos ao mesmo tempo se eles estiverem bloqueando objects diferentes , mas apenas um encadeamento pode executar um método (ou bloco) sincronizado de cada vez para qualquer object único .

volátil:

volatile é uma palavra-chave. volatile força todos os threads para obter o valor mais recente da variável da memory principal em vez do cache. Nenhum bloqueio é necessário para acessar variables ​​voláteis. Todos os threads podem acessar o valor da variável volátil ao mesmo tempo.

O uso de variables volatile reduz o risco de erros de consistência de memory, porque qualquer gravação em uma variável volátil estabelece um relacionamento acontece antes com leituras subseqüentes dessa mesma variável.

Isso significa que as alterações em uma variável volatile são sempre visíveis para outros segmentos . Além disso, isso também significa que, quando um thread lê uma variável volatile , ele não vê apenas a última alteração no volátil, mas também os efeitos colaterais do código que levaram à alteração .

Quando usar: Um thread modifica os dados e outros threads precisam ler o valor mais recente dos dados. Outros tópicos tomarão alguma ação, mas não atualizarão dados .

AtomicXXX:

AtomicXXX classs AtomicXXX suportam programação segura de thread sem bloqueio em variables ​​únicas. Essas classs AtomicXXX (como AtomicInteger ) resolvem erros de inconsistência de memory / efeitos colaterais de modificação de variables ​​voláteis, que foram acessadas em vários threads.

Quando usar: vários segmentos podem ler e modificar dados.

sincronizado:

synchronized é a palavra-chave usada para proteger um método ou bloco de código. Ao fazer o método como sincronizado tem dois efeitos:

  1. Primeiro, não é possível para duas invocações de methods synchronized no mesmo object para intercalar. Quando um encadeamento está executando um método synchronized para um object, todos os outros encadeamentos que invocam methods synchronized para o mesmo bloco de object (suspendem execução) até que o primeiro encadeamento seja feito com o object.

  2. Segundo, quando um método synchronized é encerrado, ele estabelece automaticamente um relacionamento acontece antes de qualquer chamada subseqüente de um método synchronized para o mesmo object. Isso garante que as alterações no estado do object sejam visíveis para todos os threads.

Quando usar: vários segmentos podem ler e modificar dados. Sua lógica de negócios não apenas atualiza os dados, mas também executa operações atômicas

AtomicXXX é equivalente a volatile + synchronized , embora a implementação seja diferente. AmtomicXXX estende as variables volatile + os methods compareAndSet , mas não usa a synchronization.

Questões SE relacionadas:

Diferença entre volátil e sincronizado em Java

Volátil booleano vs AtomicBoolean

Bons artigos para ler: (Acima do conteúdo é retirado destas páginas de documentação)

https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html

http://www.google.com/adwords/apav

Eu sei que dois segmentos não podem entrar no bloco Sincronizar ao mesmo tempo

Dois segmentos não podem inserir um bloco sincronizado no mesmo object duas vezes. Isso significa que dois segmentos podem entrar no mesmo bloco em objects diferentes. Essa confusão pode levar a um código como esse.

 private Integer i = 0; synchronized(i) { i++; } 

Isso não se comportará como esperado, pois pode ser bloqueado em um object diferente a cada vez.

se isso é verdade do que como este atomic.incrementAndGet () funciona sem sincronizar? e é thread seguro?

sim. Não usa bloqueio para obter segurança de thread.

Se você quiser saber como eles funcionam com mais detalhes, você pode ler o código para eles.

E qual é a diferença entre leitura e escrita interna para Variável Volátil / Variável Atômica?

A class atômica usa campos voláteis . Não há diferença no campo. A diferença é as operações realizadas. As classs Atomic usam as operações CompareAndSwap ou CAS.

Eu li em algum artigo que o segmento tem cópia local de variables ​​o que é isso?

Eu só posso supor que se refere ao fato de que cada CPU tem sua própria visão da memory em cache, que pode ser diferente de qualquer outra CPU. Para garantir que sua CPU tenha uma visão consistente dos dados, você precisa usar técnicas de segurança de thread.

Este é apenas um problema quando a memory é compartilhada, pelo menos, um segmento atualiza.

Uma volatile + synchronization é uma solução à prova de erros para que uma operação (instrução) seja totalmente atômica, o que inclui várias instruções para a CPU.

Diga por exemplo: volátil int i = 2; i ++, que não é nada além de i = i + 1; o que torna i como o valor 3 na memory após a execução desta declaração. Isso inclui ler o valor existente da memory para i (que é 2), carregar no registro do acumulador da CPU e fazer com o cálculo incrementando o valor existente com um (2 + 1 = 3 no acumulador) e, em seguida, gravar esse valor incrementado de volta para a memory. Essas operações não são suficientemente atômicas, embora o valor seja i é volátil. Eu sendo volátil garante apenas que uma única leitura / gravação da memory é atômica e não com MULTIPLE. Portanto, precisamos ter sincronizado também com o i ++ para mantê-lo como uma declaração atômica à prova de erros. Lembre-se do fato de que uma declaração inclui várias instruções.

Espero que a explicação seja clara o suficiente.

O modificador volátil de Java é um exemplo de um mecanismo especial para garantir que a comunicação ocorra entre os encadeamentos. Quando um thread grava em uma variável volátil, e outro thread vê esse write, o primeiro thread informa ao segundo sobre todo o conteúdo da memory até executar a gravação para aquela variável volátil.

As operações atômicas são executadas em uma única unidade de tarefa, sem interferência de outras operações. Operações atômicas são necessárias no ambiente multi-thread para evitar inconsistência de dados.