Pergunta

eu estava lendo Este artigo Sobre "bloqueio verificado duas vezes" e fora do tópico principal do artigo, eu queria saber por que, em algum momento do artigo, o autor usa o próximo idioma:

Listagem 7. Tentativa de resolver o problema de gravação fora de ordem

public static Singleton getInstance()  
{
    if (instance == null)
    {
        synchronized(Singleton.class) {      //1
            Singleton inst = instance;         //2
            if (inst == null)
            {
                synchronized(Singleton.class) {  //3
                    inst = new Singleton();        //4
                }
                instance = inst;                 //5
            }
        }
    }
    return instance;
}

E minha pergunta é: há algum motivo para sincronizar duas vezes algum código com o mesmo bloqueio? Tem algum propósito?

Muito obrigado antecipadamente.

Foi útil?

Solução

O objetivo de travar duas vezes era tentar Para evitar gravações fora da ordem. O modelo de memória especifica onde as reordenadas podem ocorrer, em parte em termos de bloqueios. O bloqueio garante que nenhuma gravação (incluindo nenhum no construtor singleton) pareça acontecer após a "instância = inst;" linha.

No entanto, para aprofundar o assunto, eu recomendo Artigo de Bill Pugh. E então nunca tente :)

Outras dicas

O artigo refere-se ao modelo de memória Java Pre-5.0 (JMM). Sob esse modelo, deixando um bloco sincronizado forçado a gravações para a memória principal. Portanto, parece ser uma tentativa de garantir que o objeto Singleton seja empurrado antes da referência. No entanto, ele não funciona porque a gravação em instância pode ser movida para o bloco - o motel da barata.

No entanto, o modelo pré-5.0 nunca foi implementado corretamente. 1.4 deve seguir o modelo 5.0. As aulas são inicializadas preguiçosamente, então você pode apenas escrever

public static final Singleton instance = new Singleton();

Ou melhor, não use singletons, pois eles são maus.

Jon Skeet está certo: Leia Bill Pugh's artigo. O idioma que Hans usa é a forma precisa que não vai funcionar, e não deve ser usado.

Isso é inseguro:

private static Singleton instance;

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

Isso também é inseguro:

public static Singleton getInstance()  
{
    if (instance == null)
    {
        synchronized(Singleton.class) {      //1
            Singleton inst = instance;         //2
            if (inst == null)
            {
                synchronized(Singleton.class) {  //3
                    inst = new Singleton();        //4
                }
                instance = inst;                 //5
            }
        }
    }
    return instance;
}

Nunca faça nenhum deles, nunca.

Em vez disso, sincronize todo o método:

    public static synchronized Singleton getInstance() {
      if (instance == null) {
        instance = new Singleton();
      }
      return instance;
    }

A menos que você esteja recuperando esse objeto um zilhão de vezes o segundo, o desempenho atingido, em termos reais, é insignificante.

Seguindo o John Skeet Recomendação:

No entanto, para aprofundar o assunto, eu recomendaria o artigo de Bill Pugh. E então nunca tente :)

E aqui está a chave para o segundo bloco de sincronização:

Este código coloca a construção do objeto auxiliar dentro de um bloco sincronizado interno. A idéia intuitiva aqui é que deve haver uma barreira de memória no ponto em que a sincronização é liberada, e isso deve impedir a reordenação da inicialização do objeto auxiliar e da atribuição ao ajudante de campo.

Então, basicamente, com o bloco de sincronização interna, estamos tentando "trapacear" o JMM criando a instância dentro do bloco de sincronização, para forçar o JMM a executar essa alocação antes do término do bloco de sincronização. Mas o problema aqui é que o JMM está nos levando e está movendo a Assigment que está antes do bloco de sincronização dentro do bloco de sincronização, movendo nosso problema de volta ao início.

Foi isso que eu entendi desses artigos, realmente interessante e mais uma vez obrigado pelas respostas.

Tudo bem, mas o artigo disse que

O código na Listagem 7 não funciona devido à definição atual do modelo de memória. A especificação do idioma Java (JLS) exige que o código dentro de um bloco sincronizado não seja movido de um bloco sincronizado. No entanto, ele não diz que o código não em um bloco sincronizado não pode ser movido para um bloco sincronizado.

E também parece que a JVM faz a próxima tradução para "pseudo-código" no ASM:

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {      //1
      Singleton inst = instance;         //2
      if (inst == null)
      {
        synchronized(Singleton.class) {  //3
          //inst = new Singleton();      //4
          instance = new Singleton();               
        }
        //instance = inst;               //5
      }
    }
  }
  return instance;
}

Até agora, o ponto de nenhuma gravação após a "instância = inst" não é realizado?

Vou ler agora o artigo, obrigado pelo link.

Desde o Java 5, você pode fazer o bloqueio verificado duas vezes, declarando o campo volátil.

Ver http://www.cs.umd.edu/~pugh/java/memorymodel/doublecheckedlocking.html Para uma explicação completa.

Em relação a este idioma, há um artigo muito aconselhável e esclarecedor:

http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html?page=1

Por outro lado, acho que o que Dhighwayman.Myopenid significa por que o escritor colocou um bloco sincronizado referente à mesma classe (sincronizada (Singleton.class)) dentro de outro bloco sincronizado referente à mesma classe. Isso pode acontecer como uma nova instância (Singleton Inst = Instância;) é criada dentro desse bloco e, para garantir que ele seja seguro, é necessário escrever outro sincronizado.

Caso contrário, não consigo ver nenhum sentido.

Veja a palestra do Google Tech no Modelo de memória Java Para uma introdução muito boa aos pontos mais refinados do JMM. Como está faltando aqui, eu também gostaria de apontar o blog de Jeremy Masons 'Java concorrência' esp. a postagem em Bloqueio de verificação dupla (Qualquer pessoa que esteja no mundo Java parece ter um artigo sobre isso :).

Para o Java 5 e melhor, na verdade, há uma variante dupla que pode ser melhor do que sincronizar todo o acessador. Isso também é mencionado no Declaração de travamento verificada duas vezes :

class Foo {
    private volatile Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null)
                    helper = new Helper();
            }
        }
        return helper;
    }
}

A principal diferença aqui é o uso de volátil Na declaração variável - caso contrário, ela não funciona e não funciona no Java 1.4 ou menos, de qualquer maneira.

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