O que é uma maneira eficiente de implementar um padrão singleton em Java? [fechadas]

812

O que é uma maneira eficiente de implementar um padrão singleton em Java?

Riyaz Mohammed Ibrahim
fonte
1
"Qual é uma maneira eficiente de implementar um padrão singleton em Java?" defina eficiente.
Marian Paździoch 06/04/19
medium.com/@kevalpatel2106/… . Este é o artigo completo sobre como obter segurança de thread, reflexão e serialização no padrão singleton. Esta é a boa fonte para entender os benefícios e limitações da classe singleton.
Keval Patel
Como Joshua Bloch aponta no Java eficaz, o enum singleton é o melhor caminho a percorrer. Aqui eu categorizados as várias implementações como preguiçoso / ansioso etc.
isaolmez

Respostas:

782

Use uma enumeração:

public enum Foo {
    INSTANCE;
}

Joshua Bloch explicou essa abordagem em sua palestra Effective Java Reloaded no Google I / O 2008: link para vídeo . Veja também os slides 30 a 32 de sua apresentação ( effective_java_reloaded.pdf ):

A maneira correta de implementar um Singleton serializável

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Edit: Uma parte online do "Java Efetivo" diz:

"Essa abordagem é funcionalmente equivalente à abordagem de campo público, exceto que é mais concisa, fornece o mecanismo de serialização gratuitamente e oferece uma garantia rígida contra instanciações múltiplas, mesmo em face de sofisticados ataques de serialização ou reflexão. ainda a ser amplamente adotado, um tipo de enum de elemento único é a melhor maneira de implementar um singleton ".

Stephen Denne
fonte
203
Eu acho que as pessoas deveriam começar a ver as enumerações apenas como uma classe com um recurso. se você pode listar as instâncias da sua classe em tempo de compilação, use uma enumeração.
Amir Arad
7
Pessoalmente, nem sempre acho necessário usar o padrão singleton diretamente. Às vezes, uso a injeção de dependência do Spring com um contexto de aplicativo que contém o que ele chama de singletons. Minhas classes de utilitário tendem a conter apenas métodos estáticos, e eu não preciso de nenhuma instância deles.
Stephen Denne
3
Oi, Alguém pode me dizer como esse tipo de singleton pode ser ridicularizado e testado em casos de teste. Tentei trocar a instância singleton falsa por esse tipo, mas não consegui.
Ashish Sharma
29
Acho que faz sentido, mas ainda não gosto. Como você criaria um singleton que estende outra classe? Se você usa um enum, não pode.
chharvey
11
@bvdb: Se você quer muita flexibilidade, você já estragou tudo implementando um singleton em primeiro lugar. A capacidade de criar uma instância independente quando você precisar não tem preço em si mesma.
Chao
233

Dependendo do uso, existem várias respostas "corretas".

Como o java5, a melhor maneira de fazer isso é usar um enum:

public enum Foo {
   INSTANCE;
}

Antes do java5, o caso mais simples é:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

Vamos revisar o código. Primeiro, você quer que a aula seja final. Nesse caso, usei a finalpalavra-chave para informar aos usuários que é final. Então você precisa tornar o construtor privado para impedir que os usuários criem seu próprio Foo. Lançar uma exceção do construtor impede que os usuários usem a reflexão para criar um segundo Foo. Em seguida, você cria um private static final Foocampo para armazenar a única instância e um public static Foo getInstance()método para devolvê-lo. A especificação Java garante que o construtor seja chamado apenas quando a classe for usada pela primeira vez.

Quando você tem um objeto muito grande ou código de construção pesada E também tem outros métodos ou campos estáticos acessíveis que podem ser usados ​​antes que uma instância seja necessária, então e somente então você precisa usar a inicialização lenta.

Você pode usar a private static classpara carregar a instância. O código ficaria assim:

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

Como a linha private static final Foo INSTANCE = new Foo();é executada apenas quando a classe FooLoader é realmente usada, isso cuida da instanciação lenta e é garantida a segurança de threads.

Quando você também deseja serializar seu objeto, precisa garantir que a desserialização não crie uma cópia.

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

O método readResolve()garantirá que a única instância seja retornada, mesmo quando o objeto foi serializado em uma execução anterior do seu programa.

Roel Spilker
fonte
32
A verificação da reflexão é inútil. Se outro código estiver usando a reflexão sobre partes privadas, é Game Over. Não há razão para tentar funcionar corretamente sob esse uso indevido. E se você tentar, será uma "proteção" incompleta de qualquer maneira, apenas um monte de código desperdiçado.
Wouter Coekaerts
5
> "Primeiro, você quer que a aula seja final". Alguém poderia elaborar isso, por favor?
PlagueHammer 02/03/09
2
A proteção de desserialização está completamente quebrada (acho que isso é mencionado no Java Efetivo 2º Ed).
Tom Hawtin # tackline
9
-1 Este não é absolutamente o caso mais simples, é artificial e desnecessariamente complexo. Veja a resposta de Jonathan para a solução realmente mais simples que é suficiente em 99,9% de todos os casos.
Michael Borgwardt
2
Isso é útil quando seu singleton precisa herdar de uma superclasse. Você não pode usar o padrão enum singleton nesse caso, pois as enums não podem ter uma superclasse (elas podem implementar interfaces). Por exemplo, o Google Guava usa um campo final estático quando o padrão enum singleton não é uma opção: code.google.com/p/guava-libraries/source/browse/trunk/guava/src/…
Etienne Neveu
139

Isenção de responsabilidade: Acabei de resumir todas as respostas impressionantes e as escrevi em minhas palavras.


Ao implementar o Singleton, temos 2 opções
1. Carregamento lento
2. Carregamento antecipado

O carregamento lento adiciona um pouco de sobrecarga (muito para ser honesto), portanto, use-o somente quando você tiver um objeto muito grande ou código de construção pesada E também tiver outros métodos ou campos estáticos acessíveis que possam ser usados ​​antes que uma instância seja necessária, e somente então você precisará usar a inicialização lenta. Caso contrário, escolher o carregamento antecipado é uma boa escolha.

A maneira mais simples de implementar o Singleton é

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }
}

Tudo é bom, exceto seu singleton carregado cedo. Vamos tentar o singleton carregado preguiçosamente

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

Até aí tudo bem, mas nosso herói não sobreviverá enquanto luta sozinho com vários tópicos malignos que desejam muitos exemplos de nosso herói. Então, vamos protegê-lo do mal multi-threading

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

mas não é o suficiente para proteger o herói, sério !!! É o melhor que podemos / devemos fazer para ajudar nosso herói

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

Isso é chamado de "idioma de bloqueio com verificação dupla". É fácil esquecer a afirmação volátil e difícil entender por que ela é necessária.
Para detalhes: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Agora temos certeza sobre o mal do fio, mas e a cruel serialização? Temos que garantir que, mesmo durante a desserialização, nenhum novo objeto seja criado

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

O método readResolve()garantirá que a única instância seja retornada, mesmo quando o objeto foi serializado em uma execução anterior do nosso programa.

Finalmente, adicionamos proteção suficiente contra threads e serialização, mas nosso código parece volumoso e feio. Vamos dar uma olhada no nosso herói

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Sim, este é o nosso herói mesmo :)
Como a linha private static final Foo INSTANCE = new Foo();é executada apenas quando a classe FooLoaderé realmente usada, isso cuida da instanciação lenta,

e é garantido que seja seguro para threads.

E chegamos até aqui, aqui está a melhor maneira de alcançar tudo o que fizemos é a melhor maneira possível

 public enum Foo {
       INSTANCE;
   }

Que internamente será tratado como

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

É isso aí! Não há mais medo de serialização, threads e código feio. Também ENUMS singleton são preguiçosamente inicializado .

Essa abordagem é funcionalmente equivalente à abordagem de campo público, exceto que é mais concisa, fornece o mecanismo de serialização gratuitamente e oferece uma garantia rígida contra instanciações múltiplas, mesmo em face de sofisticados ataques de serialização ou reflexão. Embora essa abordagem ainda não tenha sido amplamente adotada, um tipo de enum de elemento único é a melhor maneira de implementar um singleton.

-Joshua Bloch em "Java eficaz"

Agora você deve ter percebido por que os ENUMS são considerados a melhor maneira de implementar o Singleton e agradecemos sua paciência :)
Atualizei-o no meu blog .

xyz
fonte
3
Apenas um esclarecimento: singletons implementados usando enum são inicializados preguiçosamente. Detalhes aqui: stackoverflow.com/questions/16771373/…
2
Ótima resposta. uma última coisa, substitua o método clone para gerar exceção.
riship89
2
@xyz agradáveis explicações, eu realmente gostei e aprendi muito facilmente e espero que nunca esqueceu isso
Premraj
1
Uma das melhores respostas que já tive em vermelho no stackoverflow. Obrigado!
Shay Tsadok
1
Não é um problema de serialização com o uso de enums como um singleton: Quaisquer valores de campo membro são não serializado e, portanto, não restaurado. Consulte Especificação de serialização de objetos Java, versão 6.0 . Outro problema: Sem controle de versão - todos os tipos de enum ter um fixo serialVersionUIDde 0L. Terceiro problema: Sem personalização: Qualquer método writeObject, readObject, readObjectNoData, writeReplace e readResolve específico da classe definido por tipos de enumeração é ignorado durante a serialização e desserialização.
Basil Bourque
124

A solução publicada por Stu Thompson é válida no Java5.0 e posterior. Mas eu preferiria não usá-lo porque acho que é propenso a erros.

É fácil esquecer a afirmação volátil e difícil entender por que ela é necessária. Sem o volátil, esse código não seria mais seguro para threads devido ao antipadrão de bloqueio verificado duas vezes. Veja mais sobre isso no parágrafo 16.2.4 da Concorrência Java na Prática . Resumindo: esse padrão (anterior ao Java5.0 ou sem a instrução volátil) pode retornar uma referência ao objeto Bar que está (ainda) em um estado incorreto.

Esse padrão foi inventado para otimização de desempenho. Mas isso realmente não é mais uma preocupação real. O seguinte código de inicialização lenta é rápido e, mais importante, fácil de ler.

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}
Benno Richters
fonte
1
Justo! Estou confortável com o volátil e seu uso. Ah, e três aplausos pelo JCiP.
Stu Thompson
1
Ah, essa é aparentemente a abordagem defendida por William Pugh, da fama do FindBugz.
Stu Thompson
4
@Stu A primeira edição do Effective Java (copyright 2001) detalha esse padrão no item 48.
Pascal Thivent
9
@ Bno: Que tal tornar o construtor privado?
Xyz
2
@ AlikElzin-kilaka Não é bem assim. A instância é criada na fase de carregamento de classe do BarHolder , que é adiada até a primeira vez que é necessário. O construtor de Bar pode ser tão complicado quanto você quiser, mas não será chamado até o primeiro getBar(). (E se getBarfor chamado "muito cedo", você enfrentará o mesmo problema, independentemente de como os singleons sejam implementados.) Você pode ver o carregamento lento da classe do código acima aqui: pastebin.com/iq2eayiR
Ti Strga
95

Segmento de thread seguro em Java 5+:

class Foo {
    private static volatile Bar bar = null;
    public static Bar getBar() {
        if (bar == null) {
            synchronized(Foo.class) {
                if (bar == null)
                    bar = new Bar(); 
            }
        }
        return bar;
    }
}

EDIT : Preste atenção ao volatilemodificador aqui. :) É importante porque sem ele, outros encadeamentos não são garantidos pelo JMM (Java Memory Model) para ver alterações em seu valor. A sincronização não cuida disso - apenas serializa o acesso a esse bloco de código.

EDIT 2 : A resposta do @Bno detalha a abordagem recomendada por Bill Pugh (FindBugs) e é discutível melhor. Leia e vote também na resposta dele.

Stu Thompson
fonte
1
Onde posso aprender mais sobre o modificador volátil?
Eleven81 16/01/09
Veja os comentários de stackoverflow.com/questions/70689/…
Pascal Thivent
2
Eu acho que é importante mencionar sobre ataques de reflexão. É verdade que a maioria dos desenvolvedores não precisa se preocupar, mas parece que exemplos como esses (em singletons baseados em Enum) devem incluir código que proteja contra ataques de instanciação múltipla ou simplesmente colocar um aviso indicando tais possibilidades.
Luis.espinal 27/10/10
2
A palavra-chave volátil não é necessária aqui - pois a sincronização oferece exclusão mútua e visibilidade de memória.
Hemant
2
Por que se preocupar com tudo isso em Java 5+? Meu entendimento é que a abordagem enum fornece segurança de thread e inicialização lenta. Também é muito mais simples ... Além disso, se você quiser evitar uma enum, eu ainda evitar a abordagem classe estática aninhada ...
Alexandros
91

Esqueça a inicialização lenta , é muito problemática. Esta é a solução mais simples:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}
Jonathan
fonte
21
A variável de instância singleton também pode ser finalizada. por exemplo, final estático privado A singleton = new A ();
jatanp 16/09/08
19
Isso efetivamente é uma inicialização lenta, uma vez que o singleton estático não será instanciado até que a classe seja carregada e a classe não será carregada até que seja necessária (que será a hora certa em que você fez referência ao método getInstance () pela primeira vez.
Dan Dyer
7
Se a classe A for carregada muito antes de você desejar que a estática seja instanciada, você pode agrupar a estática em uma classe interna estática para desacoplar a inicialização da classe.
Tom Hawtin - defina
3
Concordo que esta resposta é a mais simples, e Anirudhan, não há necessidade de declarar a instância final. Nenhum outro encadeamento terá acesso à classe enquanto os membros estáticos são inicializados. isso é garantido pelo compilador; em outras palavras, toda inicialização estática é feita de maneira sincronizada - apenas um encadeamento.
INOR
4
Essa abordagem tem uma limitação: o construtor não pode lançar uma exceção.
wangf
47

Certifique-se de que você realmente precisa. Faça um google para "antipadrão singleton" para ver alguns argumentos contra ele. Não há nada inerentemente errado com isso, suponho, mas é apenas um mecanismo para expor alguns recursos / dados globais, portanto, verifique se esse é o melhor caminho. Em particular, achei a injeção de dependência mais útil, principalmente se você também estiver usando testes de unidade porque o DI permite que você use recursos simulados para fins de teste.

Neil Burroughs
fonte
você pode injetar valores simulados com o método tradicional também, mas eu acho que não é o padrão / srping caminho para que o seu trabalho extra com único ganho sendo código legado ...
tgkprog
21

Estou perplexo com algumas das respostas que sugerem a DI como uma alternativa ao uso de singletons; esses são conceitos não relacionados. Você pode usar o DI para injetar instâncias singleton ou não singleton (por exemplo, por thread). Pelo menos isso é verdade se você usa o Spring 2.x, não posso falar por outras estruturas de DI.

Portanto, minha resposta ao OP seria (com exceção do código de exemplo mais trivial):

  1. Use uma estrutura de DI como Spring, depois
  2. Faça parte de sua configuração de DI, independentemente de suas dependências serem singletons, escopo de solicitação, escopo de sessão ou qualquer outra coisa.

Essa abordagem fornece uma boa arquitetura desacoplada (e, portanto, flexível e testável), na qual o uso de um singleton é um detalhe de implementação facilmente reversível (desde que todos os singletons usados ​​sejam seguros para o segmento, é claro).

Andrew Swan
fonte
4
Talvez porque as pessoas discordam de você. Não diminuí a votação, mas discordo: acho que o DI pode ser usado para resolver os mesmos problemas que os singletons. Isso se baseia no entendimento de "singleton" como "um objeto com uma única instância acessada diretamente por um nome global", em vez de apenas "um objeto com uma única instância", o que talvez seja um pouco complicado.
Tom Anderson
2
Para expandir um pouco, considere um TicketNumbererque precisa ter uma única instância global e onde você deseja escrever uma classe TicketIssuerque contenha uma linha de código int ticketNumber = ticketNumberer.nextTicketNumber();. No pensamento singleton tradicional, a linha de código anterior teria que ser algo parecido TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. No pensamento de DI, a classe teria um construtor public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.
Tom Anderson
2
E torna-se problema de outra pessoa chamar isso de construtor. Uma estrutura de DI faria isso com um mapa global de algum tipo; uma arquitetura DI DI feita manualmente porque o mainmétodo do aplicativo (ou um de seus subordinados) criaria a dependência e depois chamaria o construtor. Essencialmente, o uso de uma variável global (ou um método global) é apenas uma forma simples do temido padrão de localizador de serviço e pode ser substituído pela injeção de dependência, como qualquer outro uso desse padrão.
6136 Tom Anderson
@ TomAnderson Estou realmente confuso sobre o motivo pelo qual as pessoas 'temem' o padrão do localizador de serviço. Eu acho que na maioria dos casos é um exagero ou não é necessário, na melhor das hipóteses, no entanto, existem casos aparentemente úteis. Com menor número de parâmetros, o DI é definitivamente o preferido, mas imagine 20 +. Dizer que o código não está estruturado não é um argumento válido, porque às vezes agrupamentos de parâmetros simplesmente não fazem sentido. Além disso, de uma perspectiva de teste de unidade, não me importo em testar o serviço, apenas a lógica de negócios dele e, se estiver codificado corretamente, isso seria fácil. Eu só vi essa necessidade em projetos de larga escala.
Brandon Ling
20

Considere realmente por que você precisa de um singleton antes de escrevê-lo. Há um debate quase religioso sobre o uso deles, que você pode facilmente encontrar se pesquisar no Google singletons em Java.

Pessoalmente, tento evitar os singletons o mais rápido possível por várias razões, novamente a maioria dos quais pode ser encontrada nos singletons do Google. Sinto que muitas vezes os singletons são abusados ​​porque são fáceis de entender por todos, são usados ​​como um mecanismo para obter dados "globais" em um design OO e são usados ​​porque é fácil contornar o gerenciamento do ciclo de vida do objeto (ou realmente pensando em como você pode fazer A de dentro de B). Veja coisas como Inversion of Control (IoC) ou Dependency Injection (DI) para obter um bom campo intermediário.

Se você realmente precisa de um, a wikipedia tem um bom exemplo de uma implementação adequada de um singleton.


fonte
Acordado. É mais uma classe fundamental que inicia o restante do seu aplicativo e, se ele foi duplicado, você terá um caos completo (por exemplo, acesso único a um recurso ou reforço da segurança). A transmissão de dados globais por todo o aplicativo é uma grande bandeira vermelha de acoplamento. Use-o quando reconhecer que realmente precisa.
Salvador Valencia
16

A seguir, são apresentadas três abordagens diferentes

1) Enum

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) Travamento verificado / carregamento preguiçoso

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3) Método estático de fábrica

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}
Abhijit Gaikwad
fonte
13

Eu uso o Spring Framework para gerenciar meus singletons. Ele não impõe o "singleton-ness" da classe (o que você realmente não pode fazer de qualquer maneira se houver vários carregadores de classe envolvidos), mas fornece uma maneira muito fácil de construir e configurar diferentes fábricas para criar diferentes tipos de objetos.

Matt
fonte
11

A Wikipedia tem alguns exemplos de singletons, também em Java. A implementação do Java 5 parece bastante completa e é segura para threads (bloqueio verificado duas vezes).

macbirdie
fonte
11

Versão 1:

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

Carregamento preguiçoso, rosca segura com bloqueio, baixo desempenho por causa de synchronized.

Versão 2:

public class MySingleton {
    private MySingleton() {}
    private static class MySingletonHolder {
        public final static MySingleton instance = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHolder.instance;
    }
}

Carregamento preguiçoso, segurança de rosca com alto desempenho e sem bloqueio.

coderz
fonte
10

Se você não precisar de carregamento lento, tente

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() { return Singleton.INSTANCE; }

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

Se você deseja um carregamento lento e deseja que seu Singleton seja seguro para threads, tente o padrão de verificação dupla

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

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

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

Como não é garantido que o padrão de verificação dupla funcione (devido a algum problema com os compiladores, não sei mais nada sobre isso.), Você também pode tentar sincronizar todo o método getInstance ou criar um registro para todos os seus Singletons.

Aleksi Yrttiaho
fonte
2
A primeira versão é melhor. Supondo que a classe não faça nada além de fornecer um singleton, ela será instanciada no mesmo ponto da segunda versão devido ao carregamento lento da classe.
Dan Dyer
1
A verificação dupla não faz sentido para uma estática. E por que você tornou público o método de clone protegido?
Tom Hawtin - defina
1
-1 sua versão do bloqueio com verificação dupla está quebrada.
assylias
5
Além disso, você precisa fazer o seu variável Singletonvolatile
MyTitle
A primeira versão é preguiçosa e segura para threads.
Miha_x64 9/01/19
9

Eu diria Enum Singleton

Singleton usando enum em Java geralmente é uma maneira de declarar enum singleton. Enum singleton pode conter variável de instância e método de instância. Por uma questão de simplicidade, observe também que, se você estiver usando qualquer método de instância, precisará garantir a segurança do encadeamento desse método, caso isso afete o estado do objeto.

O uso de um enum é muito fácil de implementar e não possui desvantagens em relação aos objetos serializáveis, que precisam ser contornados de outras maneiras.

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

Você pode acessá-lo por Singleton.INSTANCE, muito mais fácil do que chamar o getInstance()método no Singleton.

1.12 Serialização de constantes enum

As constantes enum são serializadas de maneira diferente dos objetos serializáveis ​​ou externalizáveis ​​comuns. A forma serializada de uma constante enum consiste apenas em seu nome; valores de campo da constante não estão presentes no formulário. Para serializar uma constante enum, ObjectOutputStreamescreve o valor retornado pelo método de nome da constante enum. Para desserializar uma constante enum, ObjectInputStreamlê o nome da constante do fluxo; a constante desserializada é então obtida chamando o java.lang.Enum.valueOfmétodo, passando o tipo de enum da constante junto com o nome da constante recebida como argumentos. Como outros objetos serializáveis ​​ou externalizáveis, as constantes enum podem funcionar como alvos de referências anteriores que aparecem posteriormente no fluxo de serialização.

O processo pelo qual constantes enum são serializados não pode ser personalizado: qualquer específico de classe writeObject, readObject, readObjectNoData, writeReplace, e readResolveos métodos definidos por tipos enum são ignorados durante a serialização e desserialização. Da mesma forma, quaisquer declarações de campo serialPersistentFieldsou serialVersionUIDtambém são ignoradas - todos os tipos de enum têm um valor fixo serialVersionUIDde 0L. A documentação de campos e dados serializáveis ​​para tipos de enumeração é desnecessária, pois não há variação no tipo de dados enviados.

Citado nos documentos da Oracle

Outro problema com os Singletons convencionais é que, quando você implementa a Serializableinterface, eles não permanecem mais como Singleton porque o readObject()método sempre retorna uma nova instância como construtor em Java. Isso pode ser evitado usando readResolve()e descartando a instância recém-criada substituindo por singleton como abaixo

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

Isso pode se tornar ainda mais complexo se a sua Classe Singleton mantiver o estado, pois você precisará torná-los transitórios, mas com o Enum Singleton, a serialização é garantida pela JVM.


Boa leitura

  1. Padrão Singleton
  2. Enums, singletons e desserialização
  3. Bloqueio verificado duas vezes e o padrão Singleton
NullPoiиteя
fonte
8
There are 4 ways to create a singleton in java.

1- eager initialization singleton

    public class Test{
        private static final Test test = new Test();
        private Test(){}
        public static Test getTest(){
            return test;
        }
    

2- lazy initialization singleton (thread safe)

    public class Test {
         private static volatile Test test;
         private Test(){}
         public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();
                }
            }
         }

        return test;
    


3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)

    public class Test {

        private Test(){}

        private static class TestHolder{
            private static final Test test = new Test();
        }

        public static Test getInstance(){
            return TestHolder.test;
        }
    }

4- enum singleton
      public enum MySingleton {
        INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}
Dheeraj Sachan
fonte
(1) não está ansioso, é preguiçoso devido ao mecanismo de carregamento da classe JVM.
Miha_x64 9/01/19
@ Miha_x64 quando eu disse ansioso carregamento, eu disse ansioso inicialização. Se você acha que ambos são iguais, então o que está ansioso é o carregamento. Talvez você deva escrever um livro e corrigir os erros cometidos por autores anteriores, como Joshua Bloch.
Dheeraj Sachan
Java eficaz é um ótimo livro, mas definitivamente requer edição.
Miha_x64
@ Miha_x64 o que é o carregamento ansioso, u pode explicar por exemplo
Dheeraj Sachan
Fazer algo "ansiosamente" significa "o mais rápido possível". Por exemplo, o Hibernate suporta o carregamento de relações ansiosamente, se necessário explicitamente.
Miha_x64 10/01/19
8

Pode ser um pouco tarde para o jogo, mas há muitas nuances na implementação de um singleton. O padrão do suporte não pode ser usado em muitas situações. E IMO ao usar um volátil - você também deve usar uma variável local. Vamos começar do início e iterar no problema. Você verá o que eu quero dizer.


A primeira tentativa pode ser algo como isto:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

Aqui temos a classe MySingleton, que possui um membro estático privado chamado INSTANCE, e um método estático público chamado getInstance (). Na primeira vez que getInstance () é chamado, o membro INSTANCE é nulo. O fluxo cairá na condição de criação e criará uma nova instância da classe MySingleton. As chamadas subseqüentes a getInstance () descobrirão que a variável INSTANCE já está definida e, portanto, não criará outra instância do MySingleton. Isso garante que haja apenas uma instância do MySingleton compartilhada entre todos os chamadores de getInstance ().

Mas esta implementação tem um problema. Aplicativos multithread terão uma condição de corrida na criação da instância única. Se vários encadeamentos de execução atingirem o método getInstance () aproximadamente (ou ao redor) ao mesmo tempo, cada um verá o membro INSTANCE como nulo. Isso resultará em cada thread criando uma nova instância do MySingleton e subsequentemente configurando o membro INSTANCE.


private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

Aqui, usamos a palavra-chave sincronizada na assinatura do método para sincronizar o método getInstance (). Isso certamente corrigirá nossa condição de corrida. Threads agora bloquearão e entrarão no método, um de cada vez. Mas também cria um problema de desempenho. Essa implementação não apenas sincroniza a criação da instância única, mas também sincroniza todas as chamadas para getInstance (), incluindo leituras. As leituras não precisam ser sincronizadas, pois elas simplesmente retornam o valor de INSTANCE. Como as leituras compõem a maior parte de nossas chamadas (lembre-se, a instanciação ocorre apenas na primeira chamada), teremos um impacto desnecessário no desempenho sincronizando todo o método.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

Aqui, movemos a sincronização da assinatura do método para um bloco sincronizado que envolve a criação da instância MySingleton. Mas isso resolve nosso problema? Bem, não estamos mais bloqueando as leituras, mas também demos um passo atrás. Vários encadeamentos atingirão o método getInstance () aproximadamente ou ao mesmo tempo e todos verão o membro INSTANCE como nulo. Em seguida, eles atingem o bloco sincronizado onde se obtém o bloqueio e cria a instância. Quando esse segmento sai do bloco, os outros segmentos disputam o bloqueio, e um por um cada segmento passa pelo bloco e cria uma nova instância da nossa classe. Então, estamos de volta onde começamos.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Aqui emitimos outro cheque do lado de dentro do bloco. Se o membro INSTANCE já tiver sido definido, pularemos a inicialização. Isso é chamado de bloqueio verificado duas vezes.

Isso resolve nosso problema de instanciação múltipla. Mas mais uma vez, nossa solução apresentou outro desafio. Outros threads podem não "ver" que o membro INSTANCE foi atualizado. Isso ocorre por que o Java otimiza as operações de memória. Os threads copiam os valores originais das variáveis ​​da memória principal no cache da CPU. Alterações nos valores são gravadas e lidas nesse cache. Esse é um recurso do Java projetado para otimizar o desempenho. Mas isso cria um problema para nossa implementação de singleton. Um segundo segmento - sendo processado por uma CPU ou núcleo diferente, usando um cache diferente - não verá as alterações feitas pelo primeiro. Isso fará com que o segundo segmento veja o membro INSTANCE como nulo, forçando a criação de uma nova instância do nosso singleton.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Resolvemos isso usando a palavra-chave volátil na declaração do membro INSTANCE. Isso instruirá o compilador a sempre ler e gravar na memória principal e não no cache da CPU.

Mas essa mudança simples tem um custo. Como estamos ignorando o cache da CPU, sofreremos um impacto no desempenho toda vez que operarmos no membro instável volátil - o que fazemos quatro vezes. Verificamos duas vezes a existência (1 e 2), definimos o valor (3) e depois retornamos o valor (4). Alguém poderia argumentar que esse caminho é o caso complementar, pois apenas criamos a instância durante a primeira chamada do método. Talvez um impacto no desempenho na criação seja tolerável. Mas mesmo nosso principal caso de uso, lê, operará no membro volátil duas vezes. Uma vez para verificar a existência e novamente para retornar seu valor.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

Como o impacto no desempenho deve-se a operar diretamente no membro volátil, vamos definir uma variável local para o valor do volátil e operar a variável local. Isso diminuirá o número de vezes que operamos no volátil, recuperando parte de nosso desempenho perdido. Observe que precisamos definir nossa variável local novamente quando inserirmos o bloco sincronizado. Isso garante que esteja atualizado com as alterações que ocorreram enquanto esperávamos o bloqueio.

Eu escrevi um artigo sobre isso recentemente. Desconstruindo o Singleton . Você pode encontrar mais informações sobre esses exemplos e um exemplo do padrão "suporte" lá. Há também um exemplo do mundo real que mostra a abordagem volátil verificada duas vezes. Espero que isto ajude.

Michael Andrews
fonte
Poderia explicar por que BearerToken instanceno seu artigo não é static? E o que é isso result.hasExpired()?
Woland 29/10
E o que dizer class MySingleton- talvez devesse ser final?
Woland
1
@Woland A BearerTokeninstância não é estática porque faz parte do BearerTokenFactory - que é configurado com um servidor de autorização específico. Pode haver muitos BearerTokenFactoryobjetos - cada um com seu próprio "cache" BearerTokenque ele distribui até expirar. O hasExpired()método no BeraerTokené chamado no get()método da fábrica para garantir que ele não entregue um token expirado. Se expirado, um novo token será solicitado para o servidor de autorização. O parágrafo após o bloco de código explica isso em mais detalhes.
Michael Andrews
4

Isto é como implementar um simples singleton:

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */ 
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

É assim que você pode criar corretamente o seu singleton:

public class Singleton {
    // The constructor must be private to prevent external instantiation   
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only 
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE 
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
Nicolas Filotto
fonte
Ambos são preguiçosos, assumindo que a única coisa que você precisa do singleton é sua instância.
Miha_x64 9/01/19
@ Miha_x64 o primeiro caso instancia o singleton quando a JVM inicializa a classe, o segundo caso apenas instancia o singleton ao chamar getInstance(). Mas, de fato, se você não possui outros métodos estáticos em sua classe Singletone apenas liga, getInstance()não há diferença real.
Nicolas Filotto
3

Você precisará verificar novamente o idioma se precisar carregar a variável de instância de uma classe lentamente. Se você precisar carregar uma variável estática ou um singleton preguiçosamente, precisará do idioma do titular da demanda por demanda .

Além disso, se o singleton precisar ser flexível, todos os outros campos precisarão ser transitórios e o método readResolve () precisará ser implementado para manter o objeto singleton invariável. Caso contrário, sempre que o objeto for desserializado, uma nova instância do objeto será criada. O que readResolve () faz é substituir o novo objeto lido por readObject (), que forçou a coleta de lixo desse novo objeto, pois não há variável referente a ele.

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 
Onur
fonte
3

Várias maneiras de criar objeto singleton:

  1. Conforme Joshua Bloch - Enum seria o melhor.

  2. você também pode usar o bloqueio de verificação dupla.

  3. Até a classe estática interna pode ser usada.

Shailendra Singh
fonte
3

Enum singleton

A maneira mais simples de implementar um Singleton seguro para threads é usar um Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Este código funciona desde a introdução do Enum no Java 1.5

Bloqueio duplo verificado

Se você deseja codificar um singleton “clássico” que funcione em um ambiente multithread (a partir do Java 1.5), use este.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

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

Isso não é seguro para threads antes da versão 1.5 porque a implementação da palavra-chave volátil foi diferente.

Carregamento Singleton antecipado (funciona mesmo antes do Java 1.5)

Essa implementação instancia o singleton quando a classe é carregada e fornece segurança de thread.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}
Dan Moldovan
fonte
2

Para o JSE 5.0 e superior, use a abordagem Enum, caso contrário, use a abordagem estática de suporte de singleton (uma abordagem de carregamento lento descrita por Bill Pugh). A solução mais recente também é segura para threads, sem a necessidade de construções de linguagem especiais (por exemplo, voláteis ou sincronizadas).

raoadnan
fonte
2

Outro argumento frequentemente usado contra Singletons são seus problemas de testabilidade. Singletons não são facilmente ridicularizáveis ​​para fins de teste. Se isso for um problema, eu gostaria de fazer a seguinte modificação:

public class SingletonImpl {

    private static SingletonImpl instance;

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

    public static void setInstance(SingletonImpl impl) {
        instance = impl;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

O setInstancemétodo adicionado permite definir uma implementação de maquete da classe singleton durante o teste:

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Isso também funciona com abordagens de inicialização antecipada:

public class SingletonImpl {

    private static final SingletonImpl instance = new SingletonImpl();

    private static SingletonImpl alt;

    public static void setInstance(SingletonImpl inst) {
        alt = inst;
    }

    public static SingletonImpl getInstance() {
        if (alt != null) {
            return alt;
        }
        return instance;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Isso também tem a desvantagem de expor essa funcionalidade ao aplicativo normal. Outros desenvolvedores que trabalham nesse código podem ficar tentados a usar o método 'setInstance' para alterar uma função específica e, assim, alterar todo o comportamento do aplicativo; portanto, esse método deve conter pelo menos um bom aviso no seu javadoc.

Ainda assim, para a possibilidade de teste de maquete (quando necessário), essa exposição ao código pode ser um preço aceitável a ser pago.

user3792852
fonte
1

classe singleton mais simples

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}
Rohan Kamat
fonte
1
este é o mesmo que a resposta de Jonathan abaixo
INOR
1
Duplicata desta resposta por Jonathan postada cinco anos antes. Veja essa resposta para comentários interessantes.
Basil Bourque
0

Ainda acho que, após o java 1.5, o enum é a melhor implementação de singleton disponível, pois também garante que mesmo nos ambientes com vários threads - apenas uma instância seja criada.

public enum Singleton{ INSTANCE; }

e pronto !!!

shikjohari
fonte
1
Isso já foi mencionado em outras respostas anos atrás.
Pang
0

Dê uma olhada neste post.

Exemplos de padrões de design do GoF nas principais bibliotecas do Java

Na seção "Singleton" da melhor resposta,

Singleton (reconhecível pelos métodos criacionais que retornam a mesma instância (geralmente por si só) toda vez)

  • java.lang.Runtime # getRuntime ()
  • java.awt.Desktop # getDesktop ()
  • java.lang.System # getSecurityManager ()

Você também pode aprender o exemplo de Singleton nas próprias classes nativas do Java.

kenju
fonte
0

O melhor padrão de singleton que eu já vi usa a interface do fornecedor.

  • É genérico e reutilizável
  • Suporta inicialização lenta
  • Ele é sincronizado apenas até ser inicializado; o fornecedor bloqueador é substituído por um fornecedor não bloqueador.

Ver abaixo:

public class Singleton<T> implements Supplier<T> {

    private boolean initialized;
    private Supplier<T> singletonSupplier;

    public Singleton(T singletonValue) {
        this.singletonSupplier = () -> singletonValue;
    }

    public Singleton(Supplier<T> supplier) {
        this.singletonSupplier = () -> {
            // The initial supplier is temporary; it will be replaced after initialization
            synchronized (supplier) {
                if (!initialized) {
                    T singletonValue = supplier.get();
                    // Now that the singleton value has been initialized,
                    // replace the blocking supplier with a non-blocking supplier
                    singletonSupplier = () -> singletonValue;
                    initialized = true;
                }
                return singletonSupplier.get();
            }
        };
    }

    @Override
    public T get() {
        return singletonSupplier.get();
    }
}

fonte
-3

Às vezes, um simples " static Foo foo = new Foo();" não é suficiente. Pense em algumas inserções básicas de dados que você deseja fazer.

Por outro lado, você teria que sincronizar qualquer método que instancia a variável singleton como tal. A sincronização não é ruim como tal, mas pode levar a problemas de desempenho ou bloqueio (em situações muito raras usando este exemplo. A solução é

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

Agora o que acontece? A classe é carregada através do carregador de classes. Diretamente depois que a classe foi interpretada a partir de uma matriz de bytes, a VM executa o estático {} - block. esse é o segredo: o bloco estático é chamado apenas uma vez, no momento em que a classe (nome) do pacote especificado é carregada por esse carregador de classe.

Georgi
fonte
3
Não é verdade. variáveis ​​estáticas são inicializadas junto com blocos estáticos quando a classe é carregada. Não há necessidade de dividir a declaração.
Craig P. Motlin
-5
public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException (“Already instantiated...”);
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

Como adicionamos a palavra-chave Synchronized antes de getInstance, evitamos a condição de corrida no caso em que dois threads chamam getInstance ao mesmo tempo.

somenath mukhopadhyay
fonte