Artigo de travamento verificado duplo
-
03-07-2019 - |
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.
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.
Eu cobro um monte disso aqui:
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.