Meu professor em uma classe de Java de nível superior sobre threading disse algo que eu não tinha certeza.
Ele afirmou que o código a seguir não atualizaria necessariamente a ready
variável. Segundo ele, os dois threads não necessariamente compartilham a variável estática, especificamente no caso em que cada thread (thread principal versus ReaderThread
) está rodando em seu próprio processador e, portanto, não compartilham os mesmos registradores / cache / etc e uma CPU não atualizará o outro.
Essencialmente, ele disse que é possível que ready
seja atualizado no thread principal, mas NÃO no ReaderThread
, de modo que ReaderThread
fará um loop infinito.
Ele também afirmou que era possível para o programa imprimir 0
ou 42
. Eu entendo como 42
poderia ser impresso, mas não 0
. Ele mencionou que esse seria o caso quando a number
variável fosse definida com o valor padrão.
Pensei que talvez não fosse garantido que a variável estática seja atualizada entre os threads, mas isso me parece muito estranho para Java. Tornar ready
volátil corrige este problema?
Ele mostrou este código:
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready) Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
fonte
Respostas:
Não há nada de especial sobre variáveis estáticas quando se trata de visibilidade. Se eles estiverem acessíveis, qualquer thread pode chegar até eles, então é mais provável que você veja problemas de simultaneidade porque eles estão mais expostos.
Há um problema de visibilidade imposto pelo modelo de memória da JVM. Aqui está um artigo falando sobre o modelo de memória e como as gravações se tornam visíveis para os threads . Você não pode contar com as mudanças que um encadeamento torna visível para outros encadeamentos em tempo hábil (na verdade, a JVM não tem obrigação de tornar essas alterações visíveis para você, em qualquer período de tempo), a menos que você estabeleça um relacionamento acontece antes .
Aqui está uma citação desse link (fornecida no comentário de Jed Wesley-Smith):
fonte
Ele estava falando sobre visibilidade e não deve ser interpretado tão literalmente.
Variáveis estáticas são de fato compartilhadas entre threads, mas as mudanças feitas em uma thread podem não ser visíveis para outra thread imediatamente, fazendo parecer que há duas cópias da variável.
Este artigo apresenta uma visão consistente com a forma como ele apresentou as informações:
Mas, novamente, este é simplesmente um modelo mental para pensar sobre encadeamento e volátil, não literalmente como a JVM funciona.
fonte
Basicamente, é verdade, mas na verdade o problema é mais complexo. A visibilidade dos dados compartilhados pode ser afetada não apenas pelos caches da CPU, mas também pela execução fora de ordem das instruções.
Portanto, Java define um Modelo de Memória , que indica sob quais circunstâncias os encadeamentos podem ver o estado consistente dos dados compartilhados.
No seu caso particular, adicionar
volatile
garante visibilidade.fonte
Eles são "compartilhados", é claro, no sentido de que ambos se referem à mesma variável, mas não necessariamente veem as atualizações um do outro. Isso é verdadeiro para qualquer variável, não apenas estática.
E, em teoria, as gravações feitas por outro encadeamento podem parecer estar em uma ordem diferente, a menos que as variáveis sejam declaradas
volatile
ou as gravações sejam explicitamente sincronizadas.fonte
Em um único carregador de classe, os campos estáticos são sempre compartilhados. Para definir o escopo de dados explicitamente para threads, você deseja usar um recurso como
ThreadLocal
.fonte
Quando você inicializa a variável de tipo primitivo estático, o padrão java atribui um valor para variáveis estáticas
quando você define a variável desta forma, o valor padrão de i = 0; é por isso que existe a possibilidade de obter 0. então o thread principal atualiza o valor booleano pronto para verdadeiro. uma vez que ready é uma variável estática, o thread principal e o outro thread fazem referência ao mesmo endereço de memória, então a variável ready muda. assim, o thread secundário sai do loop while e imprime o valor. ao imprimir o valor inicializado, o valor de número é 0. se o processo de thread passou por um loop while antes da variável de número de atualização de thread principal. então existe a possibilidade de imprimir 0
fonte
@dontocsata você pode voltar para seu professor e ensiná-lo um pouco :)
poucas notas do mundo real e independentemente do que você vê ou ouve. OBSERVE que as palavras abaixo referem-se a este caso particular na ordem exata mostrada.
As 2 variáveis a seguir residirão na mesma linha de cache em praticamente qualquer arquitetura conhecida.
Thread.exit
(thread principal) tem garantia de saída eexit
causar um limite de memória, devido à remoção do encadeamento do grupo de encadeamentos (e muitos outros problemas). (é uma chamada sincronizada e não vejo uma maneira única de ser implementada sem a parte de sincronização, já que o ThreadGroup também deve ser encerrado se nenhum encadeamento do daemon for deixado, etc).O encadeamento iniciado
ReaderThread
manterá o processo ativo, pois não é um daemon! Portanto,ready
enumber
será liberado junto (ou o número anterior, se ocorrer uma troca de contexto) e não há razão real para reordenar neste caso, pelo menos eu não consigo nem pensar em um. Você vai precisar de algo realmente estranho para ver qualquer coisa, mas42
. Mais uma vez, presumo que ambas as variáveis estáticas estarão na mesma linha de cache. Eu simplesmente não consigo imaginar uma linha de cache de 4 bytes OU uma JVM que não os atribuirá em uma área contínua (linha de cache).fonte