java volatile




Diferença entre volátil e sincronizado em Java (4)

Volátil é um modificador de campo , enquanto sincronizado modifica blocos de código e métodos . Assim, podemos especificar três variações de um acessador simples usando essas duas palavras-chave:

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1() acessa o valor atualmente armazenado em i1 no thread atual. Threads podem ter cópias locais de variáveis, e os dados não precisam ser os mesmos que os dados mantidos em outros threads. Em particular, outro thread pode ter atualizado i1 em seu thread, mas o valor no thread atual pode ser diferente de esse valor atualizado. Na verdade, Java tem a idéia de uma memória "principal", e essa é a memória que contém o valor "correto" atual das variáveis. Os segmentos podem ter sua própria cópia de dados para variáveis ​​e a cópia de thread pode ser diferente da memória "principal". Então, de fato, é possível que a memória "principal" tenha um valor de 1 para i1 , para que ela tenha um valor de 2 para i1 e para que ela tenha um valor de 3 para i1 se thread1 e thread2 tiverem ambos atualizados i1, mas esse valor atualizado ainda não foi propagado para a memória "principal" ou para outros segmentos.

Por outro lado, geti2() acessa efetivamente o valor de i2 da memória "principal". Uma variável volátil não pode ter uma cópia local de uma variável que seja diferente do valor atualmente mantido na memória "principal". Efetivamente, uma variável declarada volátil deve ter seus dados sincronizados em todos os encadeamentos, de modo que, sempre que você acessar ou atualizar a variável em qualquer encadeamento, todos os outros encadeamentos vejam imediatamente o mesmo valor. Geralmente, as variáveis ​​voláteis têm um maior acesso e uma sobrecarga de atualização do que as variáveis ​​"simples". Geralmente, os encadeamentos podem ter sua própria cópia de dados para melhor eficiência.

Existem duas diferenças entre volitile e synchronized.

Em primeiro lugar sincronizado obtém e libera bloqueios em monitores que podem forçar apenas um thread de cada vez para executar um bloco de código. Esse é o aspecto bastante conhecido para sincronizar. Mas sincronizado também sincroniza a memória. Na verdade, sincronizado sincroniza toda a memória da thread com a memória "principal". Portanto, executar o geti3() faz o seguinte:

  1. O segmento adquire o bloqueio no monitor para objeto isso.
  2. A memória do encadeamento libera todas as variáveis, ou seja, todas as suas variáveis ​​são efetivamente lidas da memória "principal".
  3. O bloco de código é executado (neste caso, definindo o valor de retorno para o valor atual de i3, que pode ter sido reiniciado da memória "principal").
  4. (Quaisquer alterações nas variáveis ​​normalmente seriam escritas na memória "principal", mas para geti3 () não temos alterações.)
  5. O encadeamento libera o bloqueio no monitor para o objeto isso.

Então, onde volatile apenas sincroniza o valor de uma variável entre memória de thread e memória "main", sincronizada sincroniza o valor de todas as variáveis ​​entre memória de thread e memória "principal", e bloqueia e libera um monitor para inicializar. Claramente sincronizado é provável que tenha mais sobrecarga do que volátil.

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

Eu estou querendo saber a diferença entre declarar uma variável como volatile e sempre acessando a variável em um bloco synchronized(this) em Java?

De acordo com este artigo http://www.javamex.com/tutorials/synchronization_volatile.shtml há muito a ser dito e existem muitas diferenças, mas também algumas semelhanças.

Estou particularmente interessado neste pedaço de informação:

...

  • o acesso a uma variável volátil nunca tem o potencial de bloquear: estamos apenas fazendo uma leitura ou gravação simples, assim, ao contrário de um bloco sincronizado, nunca iremos nos prender a nenhum bloqueio;
  • Como acessar uma variável volátil nunca contém um bloqueio, ela não é adequada para casos em que queremos ler-atualizar-gravar como uma operação atômica (a menos que estejamos preparados para "perder uma atualização");

O que eles querem dizer com read-update-write ? Não é uma gravação também uma atualização ou eles simplesmente significam que a atualização é uma gravação que depende da leitura?

Acima de tudo, quando é mais adequado declarar variáveis volatile vez de acessá-las através de um bloco synchronized ? É uma boa ideia usar o volatile para variáveis ​​que dependem de entrada? Por exemplo, existe uma variável chamada render que é lida através do loop de renderização e definida por um evento keypress?


tl; dr :

Existem 3 principais problemas com multithreading:

1) Condições da Corrida

2) Cache / memória obsoleta

3) Complier e otimizações de CPU

volatile pode resolver 2 e 3, mas não pode resolver 1. Bloqueios synchronized / explícitos podem resolver 1, 2 e 3.

Elaboração :

1) Considere este código inseguro de thread:

x++;

Embora pareça uma operação, na verdade é 3: ler o valor atual de x da memória, adicionar 1 a ele e salvá-lo de volta na memória. Se alguns threads tentarem fazer isso ao mesmo tempo, o resultado da operação é indefinido. Se x originalmente era 1, depois de 2 threads operando o código, ele pode ser 2 e pode ser 3, dependendo de qual encadeamento concluiu qual parte da operação antes de o controle ser transferido para o outro encadeamento. Esta é uma forma de condição de corrida .

O uso synchronized em um bloco de código o torna atômico - o que significa que as três operações acontecem de uma só vez, e não há como outro segmento entrar no meio e interferir. Portanto, se x for 1 e 2 threads tentarem pré-forma x++ , sabemos que no final será igual a 3. Por isso, resolve o problema da condição de corrida.

synchronized (this) {
   x++; // no problem now
}

Marcando x como volatile não faz x++; atômica, por isso não resolve este problema.

2) Além disso, os threads têm seu próprio contexto - isto é, eles podem armazenar em cache os valores da memória principal. Isso significa que alguns encadeamentos podem ter cópias de uma variável, mas operam em sua cópia de trabalho sem compartilhar o novo estado da variável entre outros encadeamentos.

Considere isso em um segmento, x = 10; . E um pouco mais tarde, em outro segmento, x = 20; . A alteração no valor de x pode não aparecer no primeiro encadeamento, porque o outro encadeamento salvou o novo valor em sua memória de trabalho, mas não o copiou para a memória principal. Ou que copiou para a memória principal, mas o primeiro segmento não atualizou sua cópia de trabalho. Então, se agora o primeiro thread verifica if (x == 20) a resposta será false .

Marcando uma variável como volatile basicamente, diz a todos os threads para ler e escrever operações apenas na memória principal. synchronized informa a cada thread para ir atualizar seu valor da memória principal quando eles entram no bloco e liberar o resultado de volta para a memória principal quando eles saem do bloco.

Note que diferentemente das corridas de dados, a memória obsoleta não é tão fácil de (re) produzir, já que os flushes para a memória principal ocorrem de qualquer maneira.

3) O complier e a CPU podem (sem qualquer forma de sincronização entre threads) tratar todo o código como único thread. O que significa que pode olhar para algum código, que é muito significativo em um aspecto multithreading, e tratá-lo como se fosse single threaded, onde não é tão significativo. Assim, ele pode olhar para um código e decidir, em termos de otimização, reorganizá-lo ou mesmo remover partes dele completamente, se não souber que esse código foi projetado para funcionar em vários segmentos.

Considere o seguinte código:

boolean b = false;
int x = 10;

void threadA() {
    x = 20;
    b = true;
}

void threadB() {
    if (b) {
        System.out.println(x);
    }
}

Você poderia pensar que threadB só poderia imprimir 20 (ou não imprimir qualquer coisa se threadB if-check for executado antes de definir b como true), como b é definido como true somente após x ser definido como 20, mas o compilador / CPU pode decidir reordenar threadA, nesse caso, threadB também pode imprimir 10. Marcando b como volatile garante que ele não será reordenado (ou descartado em certos casos). O que significa threadB só pode imprimir 20 (ou nada). Marcando os métodos como syncrhonized alcançará o mesmo resultado. Também marcar uma variável como volatile só garante que ela não seja reordenada, mas tudo antes / depois dela ainda pode ser reordenado, portanto, a sincronização pode ser mais adequada em alguns cenários.

Observe que, antes do Java 5 New Memory Model, o volatile não resolveu esse problema.


Eu gosto jenkov's explicação jenkov's

Visibilidade de objetos compartilhados

Se dois ou mais segmentos estão compartilhando um objeto, sem o uso adequado de declarações voláteis ou sincronização , atualizações para o objeto compartilhado feitas por um segmento podem não estar visíveis para outros segmentos.

Imagine que o objeto compartilhado seja inicialmente armazenado na memória principal. Um encadeamento em execução na CPU um lê o objeto compartilhado em seu cache de CPU. Lá, faz uma alteração no objeto compartilhado. Contanto que o cache da CPU não tenha sido liberado de volta para a memória principal, a versão alterada do objeto compartilhado não estará visível para os threads em execução em outras CPUs. Dessa forma, cada thread pode acabar com sua própria cópia do objeto compartilhado, cada cópia fica em um cache de CPU diferente.

O diagrama a seguir ilustra a situação do esboço. Um encadeamento em execução na CPU esquerda copia o objeto compartilhado em seu cache de CPU e altera sua variável de contagem para 2. Essa alteração não é visível para outros encadeamentos em execução na CPU correta, porque a atualização para contagem não foi liberada de volta para a principal memória ainda.

Para resolver esse problema, você pode usar a palavra-chave volátil do Java . A palavra-chave volátil pode garantir que uma determinada variável seja lida diretamente da memória principal e sempre gravada de volta na memória principal quando atualizada.

Condições da corrida

Se dois ou mais segmentos compartilham um objeto e mais de um segmento atualiza variáveis ​​nesse objeto compartilhado, condições de corrida podem ocorrer.

Imagine se o thread A ler a contagem de variáveis ​​de um objeto compartilhado em seu cache de CPU. Imagine também que o segmento B faz o mesmo, mas em um cache de CPU diferente. Agora o thread A adiciona um para contar e o thread B faz o mesmo. Agora var1 foi incrementado duas vezes, uma vez em cada cache da CPU.

Se esses incrementos tivessem sido realizados sequencialmente, a contagem de variáveis ​​seria incrementada duas vezes e o valor original + 2 seria gravado de volta na memória principal.

No entanto, os dois incrementos foram realizados simultaneamente sem a devida sincronização. Independentemente de qual dos segmentos A e B que gravam sua versão atualizada da contagem de volta à memória principal, o valor atualizado será apenas 1 maior que o valor original, apesar dos dois incrementos.

Este diagrama ilustra uma ocorrência do problema com condições de corrida, conforme descrito acima:

Para resolver esse problema, você pode usar um bloco sincronizado Java . Um bloco sincronizado garante que apenas um segmento possa entrar em uma determinada seção crítica do código a qualquer momento. Blocos sincronizados também garantem que todas as variáveis ​​acessadas dentro do bloco sincronizado serão lidas da memória principal, e quando o encadeamento sair do bloco sincronizado, todas as variáveis ​​atualizadas serão retornadas para a memória principal novamente, independentemente de a variável ser declarada volátil ou não.


synchronized é o modificador de restrição de acesso de nível de método / nível de bloco. Ele irá certificar-se de que um thread possui o bloqueio para a seção crítica. Apenas o encadeamento, que possui um bloqueio, pode entrar no bloco synchronized . Se outros segmentos estiverem tentando acessar essa seção crítica, eles terão que esperar até que o proprietário atual libere o bloqueio.

volatile é um modificador de acesso variável que força todos os threads a obter o último valor da variável da memória principal. Nenhum bloqueio é necessário para acessar variáveis volatile . Todos os threads podem acessar o valor da variável volátil ao mesmo tempo.

Um bom exemplo para usar variável volátil: variável de Date .

Suponha que você tenha tornado a variável de data volatile . Todos os encadeamentos, que acessam essa variável, sempre obtêm os dados mais recentes da memória principal, de modo que todos os encadeamentos mostrem o valor Real (real) da Data. Você não precisa de threads diferentes mostrando tempo diferente para a mesma variável. Todos os tópicos devem mostrar o valor correto da data.

Dê uma olhada neste article para entender melhor o conceito volatile .

Lawrence Dol cleary explicou sua read-write-update query .

Quanto às suas outras dúvidas

Quando é mais adequado declarar variáveis ​​voláteis do que acessá-las através de sincronização?

Você tem que usar o volatile se você acha que todos os threads devem obter o valor real da variável em tempo real, como o exemplo que expliquei para a variável Date.

É uma boa ideia usar o volátil para variáveis ​​que dependem de entrada?

A resposta será a mesma da primeira consulta.

Consulte este article para melhor compreensão.





volatile