Pergunta

Java 5 e acima apenas. Suponha que um computador de memória compartilhada multiprocessador (você provavelmente está usando um agora).

Aqui está um código para a inicialização preguiçosa de um singleton:

public final class MySingleton {
  private static MySingleton instance = null;
  private MySingleton() { } 
  public static MySingleton getInstance() {
    if (instance == null) {
      synchronized (MySingleton.class) {
        if (instance == null) {
          instance = new MySingleton();
        }
      }
    }
    return instance;
  }
}

Faz instance tem que ser declarado volatile Para impedir que o otimizador reescreva getInstance () da seguinte maneira (que estaria correta em um programa seqüencial):

public static MySingleton getInstance() {
  if (instance == null) {
    synchronized (MySingleton.class) {
      // instance must be null or we wouldn't be here  (WRONG!)
      instance = new MySingleton();
    }
  }
}

Supondo que o otimizador não reescreva o código, se instance não é declarado volatile ainda é garantido ser liberado na memória quando o synchronized o bloco é exitado e leia da memória quando o synchronized O bloco é inserido?

EDIT: Eu esqueci de fazer getInstance () estático. Não acho que isso mude a validade das respostas; Todos vocês sabiam o que eu quis dizer.

Foi útil?

Solução

Sim, instance deve ser declarado volatile. Mesmo assim, é aconselhável não usar o bloqueio verificado duas vezes. Ele (ou para ser preciso, o modelo de memória Java) costumava ter uma falha séria que permitia a publicação de objetos parcialmente implementados. Isso foi corrigido no Java5, ainda é um idioma obsoleto e não há mais necessidade de usá -lo - use o Idioma do titular de inicialização preguiçoso em vez de.

A partir de Concorrência de java na prática, Seção 16.2:

O verdadeiro problema com a DCL é a suposição de que a pior coisa que pode acontecer ao ler uma referência de objeto compartilhado sem sincronização é ver erroneamente um valor obsoleto (neste caso, null); Nesse caso, o idioma DCL compensa esse risco, tentando novamente com a fechadura mantida. Mas o pior caso é realmente consideravelmente pior - é possível ver um valor atual da referência, mas valores obsoletos para o estado do objeto, o que significa que o objeto pode ser visto em um estado inválido ou incorreto.

Mudanças subsequentes no JMM (Java 5.0 e posterior) permitiram que o DCL funcionasse E se resource é feito volatile, e o impacto de desempenho disso é pequeno desde volatile As leituras geralmente são apenas um pouco mais caras do que as leituras não voláteis. No entanto, este é um idioma cuja utilidade passou amplamente - as forças que o motivaram (sincronização não contendida lenta, startup lenta da JVM) não estão mais em jogo, tornando -o menos eficaz como uma otimização. O Idiom do titular de inicialização preguiçoso oferece os mesmos benefícios e é mais fácil de entender.

Outras dicas

Sim, a instância precisa ser volátil usando bloqueio verificado duas vezes em Java, porque, caso contrário, a inicialização de Mysingleton poderia expor um objeto parcialmente construído para o restante do sistema. Também é verdade que os threads sincronizam quando chegarem à declaração "sincronizada", mas isso é tarde demais neste caso.

Wikipedia E algumas outras perguntas sobre o Stack Overflow têm boas discussões sobre o "bloqueio verificado duas vezes", então eu aconselho a ler sobre isso. Eu também aconselho não usá -lo, a menos que o perfil mostre uma necessidade real de desempenho nesse código específico.

Há um idioma mais seguro e mais legível para a inicialização preguiçosa dos singletons Java:

class Singleton {

  private static class Holder {
    static final Singleton instance = create();
  }

  static Singleton getInstance() { 
    return Holder.instance; 
  }

  private Singleton create() {
    ⋮
  }

  private Singleton() { }

}

Se você usar o padrão de bloqueio verificado mais detalhado, você deve declarar o campo volatile, como outros já observaram.

Não. Não precisa ser volátil. Ver Como resolver a declaração "bloqueio verificado duas vezes está quebrado" em Java?

Tentativas anteriores falharam porque, se você pode trapacear o Java e evitar o volátil/sincronizar, o Java poderá enganá -lo e fornecer uma visão incorreta do objeto. No entanto, o novo final A semântica resolve o problema. Se você recebe uma referência de objeto (por leitura comum), pode ler com segurança o seu final Campos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top