Todo mundo está familiarizado com o modelo Singleton, e todos sabem por que é preguiçoso, faminto, etc. Mas você tem um entendimento completo do padrão de singleton? Hoje vou levá -lo para ver os singletons nos meus olhos, que podem ser diferentes do seu entendimento.
Aqui está um pequeno exemplo simples:
// simples classe pública preguiçosa singleton {// Singleton Instância Variável Private estático Singleton Instância = null; // Método de construção privado para garantir que as classes externas não possam ser instanciadas por meio do construtor privado singleton () {} // obtenha a instância singleton public static singleton getInstance () {if (instance == null) {instance = new singleton (); } System.out.println ("Eu sou um simples singleton preguiçoso!"); instância de retorno; }}É fácil ver que o código acima é inseguro no caso de multi-threading. Quando dois threads entram se (instance == null), ambos os threads julgam que a instância está vazia e, em seguida, duas instâncias serão obtidas. Este não é o singleton que queremos.
Em seguida, usamos o bloqueio para obter exclusão mútua para garantir a implementação de singletons.
// Método síncrono Public Class Lazy Public Singleton {// Singleton Instância Variável Private estático Singleton Instância = null; // Método de construção privado para garantir que as classes externas não possam ser instanciadas por meio do construtor privado singleton () {} // obtém singleton instância pública estática sincronizada singleton getInstance () {if (instance == null) {instance = new Singleton (); } System.out.println ("Eu sou um método síncrono singleton preguiçoso!"); instância de retorno; }}A adição de sincronizada garante a segurança do encadeamento, mas essa é a melhor maneira? Obviamente, não é, porque dessa maneira, toda vez que chamamos o método getInstance (), estaremos bloqueados e precisamos travá -lo quando chamarmos getInstance () na primeira vez. Obviamente, isso afeta o desempenho do nosso programa. Continuamos a encontrar maneiras melhores.
Após a análise, verificou -se que apenas garantindo que a instância = new Singleton () é a exclusão mútua de threads, a segurança do thread pode ser garantida; portanto, a versão a seguir está disponível:
// Classe pública preguiçosa de bloqueio duplo Singleton {// Singleton Instância variável Private Static Singleton Instância = null; // Método de construção privada para garantir que as classes externas não possam ser instanciadas através do construtor privado singleton () {} // obtenha a instância singleton public static singleton getInstance () {if (instance == null) {synchronized (singleton.class) {if (instance == null) {instance = singleton (); }}} System.out.println ("Eu sou um singleton preguiçoso de bloqueio duplo!"); instância de retorno; }} Desta vez, parece que não apenas resolve o problema de segurança do thread, mas não faz com que o bloqueio seja adicionado toda vez que você chamar GetInstance (), resultando em degradação do desempenho. Parece uma solução perfeita, é assim?
Infelizmente, o fato não é tão perfeito quanto pensávamos. Há um mecanismo chamado "gravações fora de ordem" no modelo de memória da plataforma Java. É esse mecanismo que causa a falha do método de travamento de verificação dupla. A chave para esse problema está na linha 5 no código acima: instance = new singleton (); Na verdade, essa linha faz duas coisas: 1. Ligue para o construtor e crie uma instância. 2. Atribua esta instância à instância da variável da instância. Mas o problema é que essas duas etapas da JVM não garantem o pedido. Isto é. Pode ser que a instância tenha sido definida como não vazia antes de ligar para o construtor. Vamos analisá -lo juntos:
Suponha que existem dois threads a e b
1. Tópico A entra no método getInstance ().
2. Como a instância está vazia neste momento, o encadeamento A entra no bloco sincronizado.
3. Thread A Executa Instância = new Singleton (); Define a instância da variável de instância como não vazia. (Observe que é antes de ligar para o construtor.)
4. A linha A e a linha B entra.
5. A Thread B verifica se a instância está vazia e não está vazia neste momento (na terceira etapa, é definida como não vazia pelo thread a). O thread B retorna uma referência à instância. (O problema surge. Nesse momento, a referência à instância não é uma instância de singleton porque o construtor não é chamado.)
6. O Thread B sai e o fio A entra.
7. O encadeamento A continua chamando o método do construtor, completa a inicialização da instância e retorna.
Não existe uma boa maneira? Deve haver uma boa maneira, vamos continuar a explorar!
// Resolva o problema da escrita não ordenada. // Método de construção privado para garantir que as classes externas não possam ser instantadas por meio de construtor privado singleton () {} // obtenha a instância singleton public static singleton getInstance () {if (instance == null) {synchronized (singleton.class) {// 1 singleton temep = instance; // 2 if (temp == null) {sincronizado (singleton.class) {// 3 temp = new singleton (); // 4} instância = temp; // 5}}} System.out.println ("Estou resolvendo a escrita não ordenada de singletons preguiçosos!"); instância de retorno; }} 1. Tópico A entra no método getInstance ().
2. Como a instância está vazia, o encadeamento A entra no primeiro bloco sincronizado na posição // 1.
3. A Thread A executa o código na posição // 2 e atribui instância à temperatura variável local. A instância está vazia, então a temperatura também está vazia.
4. Como a temperatura está vazia, o encadeamento A entra no segundo bloco sincronizado na posição // 3. (Eu pensei que esse bloqueio era um pouco redundante)
5. A linha A executa o código na posição // 4, definindo a temperatura para não vazios, mas o construtor ainda não foi chamado! (Problema de "Redação Uncordem")
6. Se o encadeamento A blocos, o encadeamento B entra no método getInstance ().
7. Como a instância está vazia, o encadeamento B tenta inserir o primeiro bloco sincronizado. Mas porque o thread A já está dentro. Portanto, é impossível entrar. Blocos de thread b.
8. A linha A é ativada e continue executando o código na posição // 4. Chame o construtor. Gerar uma instância.
9. Atribua a referência da instância da TEMP à instância. Saia de dois blocos sincronizados. Retorna a instância.
10. A linha B é ativada e entra no primeiro bloco sincronizado.
11. O Thread B executa o código na posição // 2 e atribui a instância da instância à variável local Temp.
12. O encadeamento B determina que a temperatura variável local não está vazia, então pula o bloco IF. Retorna a instância da instância.
Até agora, resolvemos o problema acima, mas de repente descobrimos que, para resolver o problema de segurança dos threads, parece que há muito fio envolto no corpo ... é bagunçado, então precisamos simplificá -lo:
// Hungry Public Class Singleton {// A variável singleton, estática, é inicializada uma vez quando a classe é carregada para garantir a segurança do thread privado estático singleton instância = new Singleton (); // Método de construção privado para garantir que as classes externas não possam ser instanciadas através do construtor. Private Singleton () {} // Obtenha a instância do objeto Singleton public static singleton getInstance () {System.out.println ("Eu sou um singleton faminto!"); instância de retorno; }}Quando vi o código acima, senti instantaneamente que o mundo estava quieto. No entanto, esse método adota o método de estilo de homem faminto, que é para os objetos de dezembro de Singleton. Uma desvantagem disso é: se o singleton da construção for grande e não for usado após a conclusão da construção, levará ao desperdício de recursos.
Existe uma maneira perfeita? Continue assistindo:
// classe interna implementa a classe pública preguiçosa singleton {classe estática privada singletonholder {// singleton variável private static singleton instância = new singleton (); } // Método de construção privado para garantir que as classes externas não possam ser instanciadas através do construtor. private singleton () {} // Obtenha a instância do objeto Singleton public static singleton getInstance () {System.out.println ("Eu sou um singleton de classe interna!"); retornar singletonsholder.instance; }}Preguiçoso (evite o desperdício de recursos acima), seguro de linha e código simples. Como o mecanismo Java estipula que o singletono de classe interno só será carregado quando o método getInstance () for chamado pela primeira vez (implementando preguiçosa) e seu processo de carregamento for seguro (implementando a segurança do thread). A instância é instanciada quando a classe interna é carregada.
Vamos falar brevemente sobre a escrita não ordenada mencionada acima. Esta é a característica da JVM. Por exemplo, declarando duas variáveis, string a; String b; JVM pode carregar um primeiro ou b. Da mesma forma, instance = new Singleton (); Pode definir a instância como não vazia antes de chamar o construtor de Singleton. Esta é uma pergunta para muitas pessoas, dizendo que um objeto de Singleton não foi instanciado, então como a instância se tornou não vazia? Qual é o seu valor agora? Se você quiser entender esse problema, deve entender como a instância da sentença = new Singleton (); é executado. Aqui está um pseudo-código para explicar para você:
Mem = alocate (); // ALOCAR MEMÓRIA PARA GOSTOS SINGLETON. instância = mem; // Observe que a instância não está vazio agora, mas ainda não foi inicializada. cTorsingleton (instância); // Ligue para o construtor da instância Singleton e Pass.
Pode-se observar que quando um thread executa a instância = MEM;, a instância não está vazia. Se outro tópico entrar no programa e julgar a instância como não vazio, ele saltará para a instância de retornar; E neste momento, o construtor de Singleton não chamou a instância, e o valor atual é o objeto de memória retornado por alocada ();. Portanto, o segundo thread não recebe um objeto Singleton, mas um objeto de memória.
O exposto acima são meus pequenos pensamentos e compreensão do modelo Singleton. Congratulo -me calorosamente a todos os grandes deuses que procuram e orientarem e criticam.