1. O que é o modo singleton
O padrão de singleton refere -se à existência de apenas uma instância durante toda a vida da aplicação. O padrão de singleton é um padrão de design amplamente usado. Ele tem muitos benefícios, o que pode evitar a criação duplicada de objetos de instância, reduzir a sobrecarga do sistema da criação de instâncias e salvar a memória.
Existem três requisitos para o modo Singleton:
2. A diferença entre o padrão de singleton e a classe estática
Primeiro, vamos entender o que é uma classe estática. Uma classe estática significa que uma classe possui métodos estáticos e campos estáticos. O construtor é modificado por privado, por isso não pode ser instanciado. A aula de matemática é uma classe estática.
Depois de saber o que é uma aula estática, vamos falar sobre a diferença entre eles:
1) Primeiro de tudo, o padrão Singleton fornecerá um objeto globalmente exclusivo. A classe estática fornece apenas muitos métodos estáticos. Esses métodos não precisam ser criados e podem ser chamados diretamente através da classe;
2) O padrão de singleton tem maior flexibilidade e os métodos podem ser substituídos, porque as classes estáticas são todos métodos estáticos, para que não possam ser substituídos;
3) Se for um objeto muito pesado, o padrão de singleton pode ter preguiça de carregar, mas as classes estáticas não podem fazê -lo;
Então, as aulas estáticas devem ser usadas e quando devemos usar o modo singleton? Primeiro de tudo, se você deseja apenas usar alguns métodos de ferramenta, é melhor usar classes estáticas. As analogias estáticas são mais rápidas que as classes de singleton, porque a ligação estática é realizada durante o período de compilação. Se você deseja manter informações de status ou acessar recursos, use o modo Singleton. Também se pode dizer que, quando você precisar de recursos orientados a objetos (como herança, polimorfismo), escolha aulas de singleton e quando você fornece apenas alguns métodos, escolha classes estáticas.
3. Como implementar o modo singleton
1. Modo Hompy Man
O chamado modo faminto é carregar imediatamente. Geralmente, as instâncias foram geradas antes de chamar o método GetInstancef, o que significa que ele foi gerado quando a classe é carregada. A desvantagem desse modelo é muito óbvia, que é que ele ocupa recursos. Quando a classe Singleton é grande, na verdade queremos usá -la e depois gerar instâncias. Portanto, esse método é adequado para classes que ocupam menos recursos e serão usadas durante a inicialização.
classe singletonhungary {private static singletonhungary singletonhungony = new singletonhungetária (); // Defina o construtor como privado para proibir a instanciação por meio de uma nova singleTonhungary () {} public estática singletonhungary getInstance () {return singletonhungey; }}2. Modo preguiçoso
O modo preguiçoso é um carregamento preguiçoso, também chamado de carregamento preguiçoso. Crie uma instância quando o programa precisar ser usado, para que a memória não seja desperdiçada. Para o modo preguiçoso, aqui estão 5 métodos de implementação. Alguns métodos de implementação são inseguros, o que significa que os problemas de sincronização de recursos podem ocorrer em um ambiente de simultaneidade com vários tacos.
Primeiro de tudo, o primeiro método é que não há problema em um único thread, mas haverá problemas em multi-threading.
// Implementação preguiçosa da classe Singleton Mode 1-Thread Uns Safe Singletonlazy1 {private Static Singletonlazy1 Singletonlazy; private singletonlazy1 () {} public static singletonlazy1 getInstance () {if (null == singletonlazy) {tente {// simular alguma preparação antes de criar o thread.sleep do objeto (1000); } catch (interruptedException e) {e.printStackTrace (); } singletonlazy = new singletonlazy1 (); } retornar singletonlazy; }}Vamos simular 10 threads assíncronos para testar:
classe pública singletonlazytest {public static void main (string [] args) {thread2 [] threadarr = new Thread2 [10]; for (int i = 0; i <threadr.length; i ++) {threadarr [i] = new Thread2 (); Threadarr [i] .start (); }}} // thread thread thread2 estende thread {@Override public void run () {System.out.println (singletonlazy1.getInstance (). Hashcode ()); }}Resultados em execução:
124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303
Você pode ver que seus códigos de hash não são todos iguais, o que significa que vários objetos são gerados em um ambiente multithread, que não atende aos requisitos do padrão Singleton.
Então, como tornar o tópico seguro? No segundo método, usamos a palavra -chave sincronizada para sincronizar o método GetInstance.
// Modo Singleton Implementação preguiçosa 2-THREAD SEGURANÇA // Definindo o método de sincronização, a eficiência é muito baixa, todo o método é bloqueado classe singletonlazy2 {private static singletonlazy2 singletonlazy; private singletonlazy2 () {} public static sincronizado singletonlazy2 getInstance () {try {if (null == singletonlazy) {// simular alguma preparação antes de criar o thread.sleep (1000); singletonlazy = novo singletonlazy2 (); }} catch (interruptEdException e) {e.printStackTrace (); } retornar singletonlazy; }}Usando a classe de teste acima, os resultados dos testes:
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
Como pode ser visto, esse método atinge a segurança do thread. No entanto, a desvantagem é que a eficiência é muito baixa e corre de maneira síncrona. Se o próximo thread desejar obter o objeto, ele deve aguardar o lançamento do thread anterior antes que ele possa continuar a executar.
Em seguida, não podemos bloquear o método, mas bloquear o código dentro, o que também pode atingir a segurança do encadeamento. Mas esse método é o mesmo que o método de sincronização e também funciona de maneira síncrona e tem muito baixa eficiência.
// Implementação Singletonlazy 3-THREAD SEGURANÇA // Definindo blocos de código síncrono, a eficiência é muito baixa e todo o bloco de código é bloqueado classe singletonlazy3 {private static singletonlazy3 singletonlazy; privado singletonlazy3 () {} public static singletonlazy3 getInstance () {try {synchronized (singletonlazy3.class) {if (null == singletonlazy) {// simular para fazer alguma preparação antes de criar um thread.sleep (1000); singletonlazy = novo singletonlazy3 (); }}} catch (interruptEdException e) {// TODO: lidar com exceção} retornar singletonlazy; }}Vamos continuar a otimizar o código. Bloqueamos apenas o código que cria o objeto, mas isso pode garantir a segurança do encadeamento?
// Implementação preguiçosa do modo Singleton 4-THELHET UNSEFE // Somente o código que cria instâncias é sincronizado definindo blocos de código de sincronização // mas ainda existem problemas de segurança de threads singletonlazy4 {private static singletonlazy4 singtonlazy; private singletonlazy4 () {} public static singletonlazy4 getInstance () {try {if (null == singletonlazy) {// code 1 // simular para fazer alguma preparação antes de criar um thread.sleep (1000); sincronizado (singletonlazy4.class) {singletonlazy = new singletonlazy4 (); // Código 2}}} Catch (interruptEdException e) {// TODO: lide a exceção} retornar singletonlazy; }}Vamos dar uma olhada nos resultados em execução:
1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303
A julgar pelos resultados, esse método não pode garantir a segurança do thread. Por que? Vamos supor que dois threads A e B vão para 'Código 1' ao mesmo tempo, porque o objeto ainda está vazio neste momento, para que ambos possam entrar no método. Primeiro, a Thread A pegue a trava e cria o objeto. Depois que o Thread B recebe o bloqueio, ele também irá para o 'Código 2' quando for lançado e um objeto é criado. Portanto, um singleton não pode ser garantido em um ambiente multithread.
Vamos continuar a otimizar. Como há um problema com o método acima, podemos apenas fazer um julgamento nulo no bloco de código de sincronização. Este método é o nosso mecanismo de trava de verificação dupla DCL.
// SLAZY MAN no modo singleton implementa a segurança de 5 threads // Definindo o bloco de código de sincronização, use o mecanismo de trava de verificação dupla DCL // usando o mecanismo de trava de verificação dupla resolve com sucesso a insegurança e a eficiência dos problemas de eficiência implementados pelo MAILMENTO DO MODO SINGLETON // DCL também é uma solução usada pela primeira vez por meio de um singleton de singleton ///////tiply, o que é o mais julgado do Singleton Mode // também é uma solução usada pela primeira vez por meio de um singleton de singleton. Quando o objeto singletonlazy5 é criado, quando o objeto SingletonLazy5 é obtido, não há necessidade de verificar o bloqueio do bloco de código de sincronização e o código subsequente e retornar diretamente o objeto SingletonLazy5 // a função da segunda se o objeto. classe singletonlazy5 {private estático volátil singletonlazy5 singletonlazy; private singletonlazy5 () {} public static singletonlazy5 getInstance () {try {if (null == singletonlazy) {// simula alguma preparação antes de criar o thread.sleep do objeto (1000); sincronizado (singletonlazy5.class) {if (null == singletonlazy) {singletonlazy = new singletonlazy5 (); }}}} catch (interruptEdException e) {} return singletonlazy; }}Resultados em execução:
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
Podemos ver que o mecanismo de bloqueio de verificação dupla do DCL resolve os problemas de eficiência e segurança do encadeamento do modo singleton de carregamento preguiçoso. Este também é o método que usamos com mais frequência.
palavra -chave volátil
Aqui, notei que, ao definir o Singletonlazy, a palavra -chave volátil é usada. Isso é para impedir que as instruções se reordenem. Por que precisamos fazer isso? Vamos dar uma olhada em um cenário:
O código vai para singletonlazy = new singletonlazy5 (); Parece ser uma frase, mas essa não é uma operação atômica (todos são executados ou todos não são executados e metade não pode ser executada). Esta frase é compilada em 8 instruções de montagem e cerca de três coisas são feitas:
1. Aloce memória para a instância singletonlazy5.
2. Inicialize o construtor singletonlazy5
3. aponte o objeto SingletonLazy para o espaço de memória alocado (observe que esta instância não é nula).
Como o compilador Java permite que o processador execute a ordem fora da ordem (fora da ordem externa) e a ordem do cache, registra-se no principal writeback da memória no JMM (Java Memory Medel) antes do JDK1.5, a ordem do segundo e terceiro pontos acima não pode ser garantida. Ou seja, a ordem de execução pode ser 1-2-3 ou 1-3-2. Se for o último, e antes de 3 ser executado e 2 não é executado, ele é trocado para o Thread 2. Nesse momento, o Singletonlazy já executou o terceiro ponto no Thread One, Singletonlazy já não está vazio, então o Thread Two toma diretamente o Singletonlazy e depois o usa e, naturalmente, relata um erro. Além disso, esse tipo de erro difícil de rastrear e difícil de reproduzir pode não ser encontrado na última semana de depuração.
O método de redação da DCL para implementar singletons é recomendado em muitos livros e livros técnicos (incluindo livros baseados em versões anteriores do JDK1.4), mas na verdade não está completamente correto. De fato, o DCL é viável em alguns idiomas (como C), dependendo se a ordem de 2 e 3 etapas pode ser garantida. Após o JDK1.5, o funcionário notou esse problema, então o JMM foi ajustado e a palavra -chave volátil foi concretada. Portanto, se o JDK for uma versão de 1,5 ou posterior, você só precisará adicionar a palavra -chave volátil à definição de singletonlazy, que pode garantir que o singletonlazy seja lido da memória principal sempre, e a reordenação pode ser proibida e você pode usar o método de escrita DCL para concluir o modo singleton. Obviamente, o volátil afetará mais ou menos o desempenho. O mais importante é que também precisamos considerar o JDK1.42 e as versões anteriores; portanto, a melhoria da escrita de padrões de singleton ainda continua.
3. Classe interna estática
Com base nas considerações acima, podemos usar classes internas estáticas para implementar o padrão Singleton, o código é o seguinte:
// implementa o modo singleton com classe de segurança estática de classes-thread singletonstaticInner {private singletonsticTicinner () {} classe estática privada singletoninner {private static singletonsticTicinner singleTonstaticInner = new SingleTataticinner (); } public static singletonstaticInner getInstance () {try {thread.sleep (1000); } catch (interruptedException e) {// TODO BLOCO DE CATCH AUTOGERATIDO E.PRINTSTACKTRACE (); } retornar singletoninner.singletonstaticInner; }}Pode -se observar que não executamos explicitamente nenhuma operações de sincronização dessa maneira, então como isso garante a segurança do thread? Como o modo Hungry Man, é um recurso que a JVM garante que os membros estáticos da classe só possam ser carregados uma vez, para que haja apenas um objeto de instância no nível da JVM. Então, a questão é: qual é a diferença entre esse método e o modelo Homgry Man? Não está carregando imediatamente? De fato, quando uma classe é carregada, sua classe interna não será carregada ao mesmo tempo. Uma classe é carregada, que ocorre quando e somente se um de seus membros estáticos (domínios estáticos, construtores, métodos estáticos etc.) for chamado.
Pode -se dizer que esse método é a solução ideal para implementar o padrão singleton.
4. Blocos de código estático
Aqui está um padrão de implementação de bloco de código estático. Esse método é semelhante ao primeiro e também é um modelo de homem faminto.
// Use blocos de código estático para implementar a classe Singleton Mode SingleTonstaticBlock {private Static SingleTonstaticBlock SingleTonstaticBlock; static {singletonstaticblock = new singletonstaticBlock (); } public static singletonstaticBlock getInstance () {return singletonstaticblock; }}5. Serialização e deserialização
Por que o LZ recomenda serialização e deserialização? Porque, embora o modo singleton possa garantir a segurança do encadeamento, vários objetos serão gerados no caso de serialização e desserialização. Execute a seguinte classe de teste,
classe pública singletonsticaticinnerializetest {public static void main (string [] args) {try {singleTonsticaticInnerSerialize serialize = singletonsticTicTicTaticinnerialize.getInstance (); System.out.println (serialize.hashcode ()); // serialize fileOutputStream fo = new FileOutputStream ("TEM"); ObjectOutputStream oo = new ObjectOutputStream (FO); oo.WriteObject (Serialize); oo.close (); fo.close (); // Deserialize fileInputStream fi = new FileInputStream ("TEM"); ObjectInputStream oi = new ObjectInputStream (fi); SingleTonsTicaticinnerialize Serialize2 = (SingleTonsticTicaticinnerSerialize) oi.readObject (); oi.close (); fi.close (); System.out.println (serialize2.hashcode ()); } catch (Exceção e) {e.printStackTrace (); }}} // Use classes internas anônimas para implementar o padrão singleton. Ao encontrar serialização e desserialização, a mesma instância não é obtida.//solve esse problema é usar o método readResolve durante a serialização, ou seja, remover parte dos comentários. classe singletonsticaticinnerialize implementa serializável { / *** 28 de março de 2018* / private estático final serialversionuid = 1L; Classe estática privada Innerclass {private Static singletonsticaticinnerialize singletonsticaticinnerialize = new singleTonsticTicaticinnerSerialize (); } public static singletonstaticinnerSerialize getInstance () {return inerclass.singletonsticaticinnerSerialize; } // Objeto protegido readResolve () {// System.out.println ("O método readResolve foi chamado"); // retornar o InnerClass.singleTonsticTicTaticinnerialize; //}}Você pode ver:
865113938
1078694789
Os resultados mostram que são de fato duas instâncias de objetos diferentes que violam o padrão de singleton. Então, como resolver esse problema? A solução é usar o método readResolve () em deserialização, remover o código de comentário acima e executá -lo novamente:
865113938
O método readResolve foi chamado
865113938
A questão é: quem é a maneira sagrada do método readResolve ()? De fato, quando a JVM desserializar e "montar" um novo objeto da memória, ele chama automaticamente esse método readResolve para retornar o objeto que especificamos e as regras de singleton são garantidas. O surgimento do readResolve () permite que os programadores controlem os objetos obtidos por meio de desserialização por conta própria.
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.