As variables ​​estáticas são compartilhadas entre os threads?

Meu professor em uma class java de nível superior na segmentação disse algo que eu não tinha certeza.

Ele afirmou que o código a seguir não necessariamente atualizaria a variável ready . De acordo com ele, os dois threads não necessariamente compartilham a variável estática, especificamente no caso de cada thread (thread principal versus ReaderThread) estar rodando em seu próprio processador e, portanto, não compartilha os mesmos registradores / cache / etc e um A CPU não atualiza a outra.

Basicamente, ele disse que é possível que o ready esteja atualizado no thread principal, mas NÃO no ReaderThread, para que o ReaderThread faça o loop infinitamente. Ele também alegou que era possível que o programa imprimisse “0” ou “42”. Eu entendo como ’42’ pode ser impresso, mas não ‘0’. Ele mencionou que este seria o caso quando a variável number é definida para o valor padrão.

Eu pensei que talvez não seja garantido que a variável estática é atualizada entre os threads, mas isso me parece muito estranho para o Java. Faz ready volátil corrigir este problema?

Ele mostrou este código:

  public class NoVisibility {  
     booleano estático privado pronto;  
     número int estático privado;  
     class estática privada ReaderThread estende Thread {   
         public void run () {  
             while (pronto!) Thread.yield ();  
             System.out.println (number);  
         }  
     }  
     public static void main (String [] args) {  
         novo ReaderThread (). start ();  
         number = 42;  
         pronto = verdadeiro;  
     }  
 } 

Não há nenhum problema de visibilidade específico para variables ​​estáticas. Existe um problema de visibilidade imposto pelo modelo de memory da JVM. Aqui está um artigo falando sobre o modelo de memory e como as gravações se tornam visíveis aos threads . Você não pode contar com as alterações que um thread torna visível para outros threads em tempo hábil (na verdade, a JVM não tem nenhuma obrigação de tornar essas mudanças visíveis para você), a menos que você estabeleça um relacionamento antes de acontecer , aqui está uma citação esse link (fornecido no comentário de Jed Wesley-Smith):

O Capítulo 17 da Especificação de Linguagem Java define a relação acontece antes de operações de memory, como leituras e gravações de variables ​​compartilhadas. Os resultados de uma gravação por um thread têm a garantia de estarem visíveis para uma leitura por outro thread somente se a operação de gravação ocorrer – antes da operação de leitura. As construções sincronizadas e voláteis, assim como os methods Thread.start () e Thread.join (), podem formar relações antes de acontecer. Em particular:

  • Cada ação em um segmento acontece antes de cada ação nesse segmento que vem depois na ordem do programa.

  • Um desbloqueio (bloqueio sincronizado ou saída de método) de um monitor acontece – antes de cada bloqueio subseqüente (bloco sincronizado ou input de método) desse mesmo monitor. E como a relação acontece antes é transitiva, todas as ações de um encadeamento antes do desbloqueio acontecem – antes de todas as ações subsequentes a qualquer encadeamento desse monitor.

  • Uma gravação em um campo volátil acontece – antes de cada leitura subseqüente desse mesmo campo. Gravações e leituras de campos voláteis têm efeitos de consistência de memory semelhantes à input e saída de monitores, mas não implicam bloqueio de exclusão mútua.

  • Uma chamada para iniciar em um encadeamento acontece antes de qualquer ação no encadeamento iniciado.

  • Todas as ações em um encadeamento acontecem antes que qualquer outro encadeamento seja retornado com êxito de uma junit nesse encadeamento.

Ele estava falando sobre visibilidade e não para ser levado muito literalmente.

Variáveis ​​estáticas são de fato compartilhadas entre threads, mas as mudanças feitas em um thread podem não ser visíveis para outro 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 a informação:

Primeiro, você precisa entender um pouco sobre o modelo de memory Java. Eu tenho lutado um pouco ao longo dos anos para explicar isso brevemente e bem. A partir de hoje, a melhor maneira que posso pensar em descrevê-lo é se você imaginar assim:

  • Cada thread em Java ocorre em um espaço de memory separado (isso é claramente falso, então fique de acordo comigo).

  • Você precisa usar mecanismos especiais para garantir que a comunicação ocorra entre esses segmentos, como faria em um sistema de transmissão de mensagens.

  • Gravações de memory que acontecem em um thread podem “vazar” e ser vistas por outro thread, mas isso não é de forma alguma garantido. Sem comunicação explícita, você não pode garantir quais gravações são vistas por outros segmentos ou até mesmo a ordem em que elas são vistas.

modelo de rosca

Mas, novamente, isso é simplesmente um modelo mental para pensar em encadeamento e volátil, não literalmente como a JVM funciona.

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 de instruções fora de ordem.

Portanto, o Java define um Modelo de Memória , que determina sob quais circunstâncias os encadeamentos podem ver o estado consistente dos dados compartilhados.

Em seu caso particular, adicionando visibilidade de garantias volatile .

Eles são “compartilhados”, é claro, no sentido de que ambos se referem à mesma variável, mas eles não necessariamente vêem as atualizações uns dos outros. Isso é verdade para qualquer variável, não apenas estática.

E, em teoria, as gravações feitas por outro thread podem parecer estar em uma ordem diferente, a menos que as variables ​​sejam declaradas volatile ou as gravações estejam explicitamente sincronizadas.

Dentro de um único carregador de class, os campos estáticos são sempre compartilhados. Para explicitamente escopo de dados para segmentos, você gostaria de usar um recurso como o ThreadLocal .

Quando você inicializa a variável do tipo primitiva estática, o java default atribui um valor para variables ​​estáticas

 public static int i ; 

quando você define a variável como esta, o valor padrão de i = 0; é por isso que existe a possibilidade de obter 0. então o thread principal atualiza o valor de boolean ready to true. já que ready é uma variável estática, thread principal e a outra referência de thread para o mesmo endereço de memory para que a variável pronta mude. então o thread secundário sai do loop while e do valor de impressão. ao imprimir o valor valor inicializado de number é 0. se o processo thread tiver passado enquanto loop antes da variável do número de atualização do thread principal. então existe a possibilidade de imprimir 0

@dontocsata você pode voltar para o seu professor e ensiná-lo um pouco 🙂

poucas notas do mundo real e independentemente do que você vê ou é contado. Por favor note, as palavras abaixo são sobre este caso específico na ordem exata mostrada.

A seguinte variável 2 residirá na mesma linha de cache em praticamente qualquer arquitetura conhecida.

 private static boolean ready; private static int number; 

Thread.exit (thread principal) é garantido para sair e exit é garantido para causar uma cerca de memory, devido à remoção de thread de grupo de segmento (e muitos outros problemas). (é uma chamada sincronizada, e não vejo uma única maneira de ser implementada sem a parte de synchronization, pois o ThreadGroup também deve terminar se nenhum encadeamento do daemon for deixado, etc.).

O thread iniciado ReaderThread vai manter o processo ativo, já que não é um daemon! Assim, o number e o ready serão liberados juntos (ou o número antes de ocorrer uma mudança de contexto) e não há razão real para reordenar, neste caso, pelo menos, não consigo nem pensar em um. Você precisará de algo realmente estranho para ver qualquer coisa, menos 42 . Novamente presumo que ambas as variables ​​estáticas estarão na mesma linha de cache. Eu simplesmente não consigo imaginar uma linha de cache com 4 bytes de comprimento OU uma JVM que não os atribua em uma área contínua (linha de cache).