Pergunta

Referindo -se ao meu Pergunta anterior em objetos incompletamente construídos, Eu tenho uma segunda pergunta. Como Jon Skeet apontou, há uma barreira implícita de memória no final de um construtor que garante que isso final Os campos são visíveis para todos os threads. Mas e se um construtor chama outro construtor; Existe uma barreira de memória no final de cada uma delas, ou apenas no final da que foi chamada em primeiro lugar? Isto é, quando a solução "errada" é:

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}

E o correto seria uma versão do método de fábrica:

public class SafeListener {
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        }
    }

    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

O seguinte funcionaria também, ou não?

public class MyListener {
    private final EventListener listener;

    private MyListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        }
    }

    public MyListener(EventSource source) {
        this();
        source.register(listener);
    }
}

Atualizar: A questão essencial é que isso é this() garantido para realmente ligar O construtor privado acima (nesse caso, haveria a barreira onde pretendia e tudo estaria seguro), ou é possível que o construtor privado fique inlined no público como uma otimização para salvar uma barreira de memória (nesse caso, não haveria uma barreira até que no final do construtor público)?

São as regras de this() definido com precisão em algum lugar? Caso contrário, acho que devemos assumir que os construtores encadeados são permitidos, e provavelmente alguns JVMs ou talvez até javacestá fazendo isso.

Foi útil?

Solução

Eu acho que é seguro, pois o modelo de memória Java afirma que:

Deixar o ser um objeto e c ser um construtor para o em que um campo final f está escrito. Uma ação de congelamento no campo final f do o acontece quando c sai, normalmente ou abruptamente. Observe que se um construtor invocar outro construtor e o construtor invocado define um campo final, o congelamento para o campo final ocorre no final do construtor invocado.

Outras dicas

Um objeto é considerado completamente inicializado quando o construtor termina.

Isso se aplica também a construtores acorrentados.

Se você precisar se registrar no construtor, defina o ouvinte como uma classe interna estática. Isso é seguro.

Sua segunda versão não está correta, porque está permitindo que a referência 'essa' escape do processo de construção. Ter 'essa' escape invalida as garantem a segurança da inicialização que dão aos campos finais sua segurança.

Para abordar a questão implícita, a barreira no final da construção ocorre apenas no final da construção de objetos. A intuição que um leitor oferecido sobre a inline é útil; Da perspectiva do modelo de memória Java, os limites do método não existem.

EDITAR Após o comentário que sugeriu o compilador que envolve o construtor privado (não pensei nessa otimização), é provável que o código seja inseguro. E a pior parte do código multithread inseguro é que isso parece funcionar, então é melhor você evitá -lo completamente. Se você deseja reproduzir truques diferentes (você realmente deseja evitar a fábrica por algum motivo), considere adicionar um invólucro para garantir a coerência dos dados no objeto de implementação interna e registrar -se no objeto externo.


Meu palpite é que será frágil, mas ok. O compilador não pode saber se o construtor interno será chamado apenas de outros construtores ou não, portanto, deve garantir que o resultado esteja correto para o código chamando apenas o construtor interno; portanto, qualquer mecanismo que ele use (barreira de memória?) estar no lugar lá.

Eu acho que o compilador adicionaria a barreira da memória no final de todo e qualquer construtor. O problema ainda está lá: você está passando pelo this A referência a outro código (possivelmente outros threads) antes de ser totalmente construído-isso é ruim-, mas se a única 'construção' que resta estiver registrando o ouvinte, o estado do objeto será tão estável quanto jamais será.

A solução é frágil Nesse outro dia, você ou algum outro programador pode precisar adicionar outro membro ao objeto e pode esquecer que os construtores acorrentados são um truque de simultaneidade e podem decidir inicializar o campo no construtor público, e ao fazê -lo adicionará um Difícil detectar a corrida de dados em potencial em seu aplicativo, então eu tentaria evitar esse construto.

BTW: A segurança adivinhada pode estar errada. Não sei o quão complexo é o compilador e se a barreira da memória (ou similares) é algo que ele pode tentar otimizar ... já que o construtor é privado, o compilador tem informações suficientes para saber que é Somente chamado de outros construtores, e isso é informações suficientes para determinar que o mecanismo de sincronização não é necessário no construtor interno ...

A referência de objeto escape no c-TOR pode publicar um objeto incompletamente construído. Isso é verdade mesmo Se a publicação for a última declaração no construtor.

Seu SafeListener pode não se comportar bem em um ambiente simultâneo, mesmo que o c-c-TOR seja realizado (o que eu acho que não é-pense em criar objetos usando a reflexão acessar o c-TOR privado).

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