Java Singleton e sincronização

117

Por favor, esclareça minhas dúvidas sobre Singleton e Multithreading:

  • Qual é a melhor maneira de implementar Singleton em Java, em um ambiente multithread?
  • O que acontece quando vários threads tentam acessar o getInstance() método ao mesmo tempo?
  • Podemos fazer singleton getInstance() synchronized?
  • A sincronização é realmente necessária, ao usar classes Singleton?
RickDavis
fonte

Respostas:

211

Sim, é necessário. Existem vários métodos que você pode usar para obter segurança de thread com inicialização lenta:

Sincronização Draconiana:

private static YourObject instance;

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

Esta solução requer que cada thread seja sincronizado quando, na realidade, apenas os primeiros precisam ser.

Verifique a sincronização :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Essa solução garante que apenas os primeiros poucos threads que tentarem adquirir seu singleton tenham que passar pelo processo de aquisição do bloqueio.

Inicialização sob demanda :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Essa solução aproveita as garantias do modelo de memória Java sobre a inicialização de classe para garantir a segurança do thread. Cada classe só pode ser carregada uma vez e só será carregada quando for necessário. Isso significa que a primeira vez que getInstanceé chamada, InstanceHolderserá carregada e instancecriada, e como isso é controlado por ClassLoaders, nenhuma sincronização adicional é necessária.

Jeffrey
fonte
23
Aviso - tenha cuidado com a sincronização verificada duas vezes. Ele não funciona corretamente com JVMs pré-Java 5 devido a "problemas" com o modelo de memória.
Stephen C
3
-1 Draconian synchronizatione Double check synchronizationgetInstance () - o método deve ser estático!
Grim
2
@PeterRader Eles não precisam ser static, mas faria mais sentido se fossem. Alterado conforme solicitado.
Jeffrey
4
Não é garantido que sua implementação de bloqueio com verificação dupla funcione. Na verdade, está explicado no artigo que você citou para o bloqueio com verificação dupla. :) Há um exemplo usando volátil que funciona corretamente para 1.5 e superior (o bloqueio de verificação dupla está simplesmente quebrado abaixo de 1.5). A inicialização sob demanda, também citada no artigo, provavelmente seria uma solução mais simples em sua resposta.
preso
2
@MediumOne AFAIK, rnão é necessário para correção. É apenas uma otimização para evitar o acesso ao campo volátil, já que é muito mais caro do que acessar uma variável local.
Jeffrey
69

Este padrão faz uma inicialização lenta segura com thread da instância sem sincronização explícita!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Funciona porque usa o carregador de classes para fazer toda a sincronização gratuitamente: a classe MySingleton.Loaderé acessada primeiro dentro do getInstance()método, portanto, a Loaderclasse é carregada quando getInstance()é chamada pela primeira vez. Além disso, o carregador de classes garante que toda a inicialização estática seja concluída antes de você obter acesso à classe - isso é o que oferece segurança de thread.

É como mágica.

Na verdade, é muito semelhante ao padrão de enum de Jhurtado, mas acho o padrão de enum um abuso do conceito de enum (embora funcione)

Boêmio
fonte
11
A sincronização ainda está presente, é apenas aplicada pela JVM em vez de pelo programador.
Jeffrey
@Jeffrey Você está certo, é claro - eu estava digitando tudo (veja as edições)
Boêmio
2
Eu entendo que não faz diferença para a JVM, só estou dizendo que fez diferença para mim no que diz respeito ao código autodocumentado. Eu apenas nunca vi tudo em maiúsculas em Java sem a palavra-chave "final" antes (ou enum), tenho um pouco de dissonância cognitiva. Para alguém que está programando Java em tempo integral, provavelmente não faria diferença, mas se você alternar as linguagens para frente e para trás, será útil ser explícito. Idem para iniciantes. Embora, eu tenho certeza que pode se adaptar a este estilo muito rápido; tudo em maiúsculas é provavelmente o suficiente. Não quero dizer nada, gostei do seu post.
Ruby
1
Excelente resposta, embora eu não tenha entendido parte dela. Você pode elaborar "Além disso, o carregador de classe garante que toda a inicialização estática seja concluída antes de você obter acesso à classe - isso é o que lhe dá segurança de thread." , como isso ajuda na segurança do thread, estou um pouco confuso sobre isso.
gaurav jain
2
@ wz366 na verdade, embora não seja necessário, concordo por motivos de estilo (uma vez que é efetivamente final porque nenhum outro código pode acessá-lo) finaldeve ser adicionado. Feito.
Boêmio
21

Se você está trabalhando em um ambiente multithread em Java e precisa garantir que todos esses threads acessem uma única instância de uma classe, você pode usar um Enum. Isso terá a vantagem adicional de ajudá-lo a lidar com a serialização.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

e então faça com que seus threads usem sua instância como:

Singleton.SINGLE.myMethod();
Jhurtado
fonte
8

Sim, você precisa fazer getInstance()sincronizado. Se não for, pode surgir uma situação em que várias instâncias da classe podem ser feitas.

Considere o caso em que você tem dois threads que chamam getInstance()ao mesmo tempo. Agora imagine que T1 é executado logo após a instance == nullverificação e, em seguida, T2 é executado. Neste momento, a instância não foi criada ou configurada, então T2 passará na verificação e criará a instância. Agora imagine que a execução volte para T1. Agora o singleton está criado, mas T1 já fez a verificação! Ele continuará a fazer o objeto novamente! Fazer getInstance()sincronizado evita esse problema.

Existem algumas maneiras de tornar os singletons thread-safe, mas fazer a getInstance()sincronização é provavelmente a mais simples.

Oleksi
fonte
Isso ajudará colocando o código de criação de objeto no bloco sincronizado, em vez de fazer a sincronização de método inteiro?
RickDavis
@RaoG Não. Você quer a verificação e a criação no bloco de sincronização. Você precisa que essas duas operações ocorram juntas sem interrupções ou a situação que descrevi acima pode acontecer.
Oleksi
7

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 verificado duas vezes

Se você deseja codificar um singleton “clássico” que funciona em um ambiente multithread (a partir do Java 1.5), você deve usar 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 thread antes de 1.5 porque a implementação da palavra-chave volatile era diferente.

Carregamento antecipado do Singleton (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

Você também pode usar o bloco de código estático para instanciar a instância no carregamento da classe e evitar os problemas de sincronização de encadeamento.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}
usha
fonte
@Vimsha Algumas outras coisas. 1. Você deve fazer instance2. Você deve fazer getInstance()estático.
John Vint
O que você faria se quisesse criar um thread no singleton.
Arun George
@arun-george use um pool de thread, um pool de thread único se necessário e coloque um while (true) -try-catch-throwable se quiser garantir que seu thread nunca morra, não importa o erro?
tgkprog
0

Qual é a melhor maneira de implementar Singleton em Java, em um ambiente multithread?

Consulte esta postagem para obter a melhor maneira de implementar o Singleton.

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

O que acontece quando vários threads tentam acessar o método getInstance () ao mesmo tempo?

Depende da maneira como você implementou o método. Se você usar bloqueio duplo sem variável volátil, poderá obter o objeto Singleton parcialmente construído.

Consulte esta pergunta para obter mais detalhes:

Por que o volátil é usado neste exemplo de bloqueio com verificação dupla

Podemos fazer getInstance () do singleton sincronizado?

A sincronização é realmente necessária, ao usar classes Singleton?

Não é necessário se você implementar o Singleton das maneiras abaixo

  1. inicialização estática
  2. enum
  3. LazyInitalaization com Initialization-on-demand_holder_idiom

Consulte esta questão para obter mais detalhes

Java Singleton Design Pattern: Perguntas

Ravindra babu
fonte
0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Fonte: Java Efetivo -> Item 2

Sugere usá-lo, se tiver certeza de que a classe sempre permanecerá solteira.

Ashish
fonte