English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Padrão de design do Java - Padrão Singleton

Padrão Singleton no Java

Prólogo:

Durante o processo de desenvolvimento de software, frequentemente temos alguns objetos que precisamos apenas de um, como: pool de threads (threadpool), cache (cache), diálogos, configurações preferenciais, etc. Se criarmos várias instâncias desses objetos, pode levar a problemas desnecessários, como comportamento anormal do programa, uso excessivo de recursos, etc. Neste caso, o padrão Singleton pode garantir que uma classe tenha apenas uma instância e forneça um ponto de acesso global. A seguir, vamos explorar como implementar o padrão Singleton a partir de uma classe Singleton simples.

/**
 * 最经典的单例类
 */
public class Singleton {
  // 设置为静态变量来记录Singleton的唯一实例
  private static Singleton singleInstance;
  private Singleton(){
    // 构造方法声明为私有的,这样只能在Singleton类中才能调用此构造方法
  }
  /*
   * 获取Singleton对象,如果还未实例化则实例化一个对象并返回这个实例
   */
  public static Singleton getInstance(){
    if (singleInstance == null) {
      singleInstance = new Singleton();
    }
    return singleInstance;
  }
  // Outros métodos
}

从上面的例子可以看出Singleton类自己管理这个类的实例化过程,并且提供了全局访问点,即设置为静态的getInstance()方法,在其他类需要使用Singleton时,它会返回一个实例。这种单例模式有一个优点就是延迟实例化,简单来说延迟实例化就是延迟初始化,在类需要时才创建其实例,而不是在开始加载这个类时就创建出一个实例,这样的好处是可以避免性能的浪费。例如,有些对象不需要程序一开始就使用,或者在其程序执行的过程中就没有使用过。但是此例子还有一个缺点,那就是线程不够安全。因为如果有多个线程同时执行到getInstance()方法,而Singleton又还未new Singleton()一个实例,那么线程就会都认为singleInstance为null,就都会实例化Singleton,这时就会产生多个Singleton实例,明显不符合单例模式的初衷。那么接下来可能要做的就是对其进行改进

public class SingletonA {
  private static SingletonA singletongA;
  private SingletonA(){
  }
  /*
   * 增加synchronized关键字把getSingletonA方法变为同步方法
   */
  public static synchronized SingletonA getInstanceA(){
    if (singletongA == null) {
      singletongA = new SingletonA();
    }
    return singletongA;
  }
  // Outros métodos
}

A partir deste exemplo, a adição de synchronized torna o getInstanceA() um método sincronizado, o que faz com que a thread aguarde a saída de outra thread antes de entrar neste método, permitindo que apenas um thread execute o método ao mesmo tempo.

Pode ser que o problema esteja resolvido, mas é importante saber que métodos sincronizados afetam a eficiência de execução do programa. Neste exemplo, o objetivo é resolver que o primeiro exemplo de execução de getInstance() não gere várias instâncias, enquanto neste exemplo, cada vez que precisar de uma instância, será necessário chamar o método sincronizado getInstanceA(). Isso se torna uma sobrecarga, porque já não precisamos nos preocupar com a criação de novas instâncias da classe singleton. Portanto, precisamos fazer algumas melhorias.

Como mencionado acima sobre a instância atrasada, se não for usada, isso torna as coisas mais simples.

public class SingletonB {
  // Cria o singleton em inicializador estático (static initializer), garantindo a segurança de thread
  private static SingletonB singletonB = new SingletonB();
  private SingletonB(){
    // Construtor
  }
  public static SingletonB getInstaceB(){
    // Já foi instanciado, use diretamente
    return singletonB;
  }
}

Essa abordagem cria uma instância imediatamente quando o JVM carrega essa classe, porque o JVM cria a instância antes que a thread acesse essa instância, então é seguro. Mas isso pode resultar em desperdício de recursos em comparação com a instância atrasada. Além disso, se o tamanho deste objeto for grande, pode alongar o tempo de inicialização do programa.

Então, será possível usar a instância atrasada sem causar segurança de thread e aumentar a eficiência de acesso? Vamos melhorar isso com double-check locking.

/**
 * Padrão de Singleton com duplo bloqueio
 */
public class SingletonC {
  private volatile static SingletonC singletonC;
  private SingletonC(){
  }
  public static SingletonC getInstanceC(){
    if (singletonC == null) {
      synchronized (SingletonC.class) {
        if (singletonC == null) {
          singletonC = new SingletonC();
        }
      }
    }
    return singletonC;
  }
}

O exemplo acima verifica primeiro a instância, se não existir, entra na seção de sincronização, entra na seção de sincronização e verifica novamente, se ainda for null, cria a instância, então singletonC = new SingletonC() será executado apenas uma vez, e a chamada posterior getInstanceC() retornará diretamente devido à instância existente, então, além da primeira chamada, não seguirá o caminho da sincronização como no segundo exemplo. Isso pode reduzir o tempo de execução de getInstanceC(). Aparentemente, há uma palavra-chave volatile, cuja função é tornar singletonC visível para todas as threads após a inicialização, permitindo que várias threads tratem corretamente a variável SingletonC. Mas atenção: a palavra-chave volatile pode ser usada apenas em Java 5e suas subsequências, se antes dessa versão isso causará a falha no double-check.

Ao usar o padrão Singleton, se houver vários carregadores de classe (classloader), é necessário especificar manualmente o carregador de classe e especificar usar um único carregador de classe. Porque cada carregador de classe define um espaço de nomes, diferentes carregadores de classe podem carregar a mesma classe, resultando na criação de várias instâncias da classe Singleton.

Agradecemos a leitura, esperamos ajudar a todos, obrigado pelo apoio ao site!

Declaração: O conteúdo deste artigo é de origem na internet, pertence ao respectivo detentor dos direitos autorais, o conteúdo é contribuído e carregado voluntariamente pelos usuários da internet, o site não possui direitos de propriedade, não foi editado manualmente e não assume responsabilidade legal relevante. Se você encontrar conteúdo suspeito de violação de direitos autorais, por favor, envie e-mail para: notice#oldtoolbag.com (ao enviar e-mail, substitua # por @ para denunciar e forneça provas. Caso seja confirmado, o site deletará imediatamente o conteúdo suspeito de violação de direitos autorais.)

Você também pode gostar