CodeGym /Blogue Java /Random-PT /Classe Java Singleton
John Squirrels
Nível 41
San Francisco

Classe Java Singleton

Publicado no grupo Random-PT
Oi! Hoje vamos nos aprofundar nos detalhes de vários padrões de projeto, começando com o padrão Java Singleton. Vamos revisar: o que sabemos sobre padrões de projeto em geral? Padrões de projeto são práticas recomendadas que podemos aplicar para resolver vários problemas conhecidos. Os padrões de design geralmente não estão vinculados a nenhuma linguagem de programação. Pense neles como um conjunto de recomendações para ajudá-lo a evitar erros e evitar reinventar a roda.Padrões de design: Singleton - 1

O que é um singleton em Java?

Singleton é um dos padrões de design de nível de classe mais simples. Às vezes as pessoas dizem "esta classe é singleton", o que significa que a classe implementa o padrão de design singleton. Às vezes é necessário escrever uma classe onde restringimos a instanciação a um único objeto. Por exemplo, uma classe responsável por registrar ou conectar a um banco de dados. O padrão de design singleton descreve como podemos conseguir isso. Um singleton é um padrão de design que faz duas coisas:
  1. Ele garante que sempre haverá apenas uma instância da classe.

  2. Ele fornece um único ponto de acesso global a essa instância.

Portanto, existem dois recursos que são característicos de quase todas as implementações do padrão singleton:
  1. Um construtor privado. Isso limita a capacidade de criar objetos da classe fora da própria classe.

  2. Um método estático público que retorna a instância da classe. Esse método é chamado getInstance . Este é o ponto de acesso global à instância da classe.

Opções de implementação

O padrão de projeto singleton é aplicado de várias maneiras. Cada opção é boa e ruim à sua maneira. Como sempre, não existe uma opção perfeita aqui, mas devemos nos esforçar para encontrar uma. Em primeiro lugar, vamos decidir o que é bom e ruim e quais métricas afetam como avaliamos as várias implementações do padrão de design. Vamos começar com o bom. Aqui estão os fatores que tornam uma implementação mais suculenta e atraente:
  • Inicialização preguiçosa: a instância não é criada até que seja necessária.

  • Código simples e transparente: essa métrica, claro, é subjetiva, mas é importante.

  • Segurança de thread: operação correta em um ambiente multithread.

  • Alto desempenho em um ambiente multithread: pouco ou nenhum bloqueio de thread ao compartilhar um recurso.

Agora os contras. Listaremos os fatores que colocam uma implementação em uma situação ruim:
  • Sem inicialização preguiçosa: quando a classe é carregada quando o aplicativo é iniciado, independentemente de ser necessário ou não (paradoxalmente, no mundo da TI é melhor ser preguiçoso)

  • Código complexo e difícil de ler. Essa métrica também é subjetiva. Se seus olhos começarem a sangrar, presumiremos que a implementação não é a melhor.

  • Falta de segurança do fio. Em outras palavras, "perigo de discussão". Operação incorreta em um ambiente multiencadeado.

  • Baixo desempenho em um ambiente multiencadeado: os encadeamentos bloqueiam uns aos outros o tempo todo ou frequentemente ao compartilhar um recurso.

Código

Agora estamos prontos para considerar várias opções de implementação e indicar os prós e contras:

Simples


public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
A implementação mais simples. Prós:
  • Código simples e transparente

  • Segurança do fio

  • Alto desempenho em um ambiente multi-threaded

Contras:
  • Nenhuma inicialização preguiçosa.
Na tentativa de corrigir a falha anterior, obtemos a implementação número dois:

Inicialização preguiçosa


public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Prós:
  • Inicialização preguiçosa.

Contras:
  • Não thread-safe

Essa implementação é interessante. Podemos inicializar preguiçosamente, mas perdemos a segurança do thread. Não se preocupe — sincronizamos tudo na implementação número três.

acesso sincronizado


public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Prós:
  • Inicialização preguiçosa.

  • Segurança do fio

Contras:
  • Baixo desempenho multithread

Excelente! Na implementação número três, restauramos a segurança do encadeamento! Claro, é lento... Agora o método getInstance está sincronizado, então ele pode ser executado por apenas uma thread por vez. Em vez de sincronizar o método inteiro, na verdade só precisamos sincronizar a parte dele que inicializa a nova instância. Mas não podemos simplesmente usar um bloco sincronizado para envolver a parte responsável pela criação da nova instância. Fazer isso não garantiria a segurança do encadeamento. É tudo um pouco mais complicado. A sincronização adequada pode ser vista abaixo:

Bloqueio duplamente verificado


public class Singleton {
    private static final Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
Prós:
  • Inicialização preguiçosa.

  • Segurança do fio

  • Alto desempenho em um ambiente multi-threaded

Contras:
  • Não suportado em versões anteriores do Java abaixo de 1.5 (o uso da palavra-chave volátil é corrigido desde a versão 1.5)

Observe que, para que essa opção de implementação funcione corretamente, uma das duas condições deve ser satisfeita. A variável INSTANCE deve ser final ou volátil . A última implementação que discutiremos hoje é o titular da classe singleton .

titular da classe


public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
Prós:
  • Inicialização preguiçosa.

  • Segurança do fio.

  • Alto desempenho em um ambiente multi-threaded.

Contras:
  • A operação correta requer uma garantia de que o objeto singleton seja inicializado sem erros. Caso contrário, a primeira chamada para o método getInstance resultará em um ExceptionInInitializerError e todas as chamadas subsequentes produzirão um NoClassDefFoundError .

Esta implementação é quase perfeita. É preguiçoso, thread-safe e rápido. Mas tem uma nuance, conforme explicado na lista de contras. Comparação de várias implementações do padrão singleton:
Implementação Inicialização preguiçosa Segurança do fio Desempenho multithread Quando usar?
Simples - + Rápido Nunca. Ou possivelmente quando a inicialização preguiçosa não é importante. Mas nunca seria melhor.
Inicialização preguiçosa + - Não aplicável Sempre quando multithreading não é necessário
acesso sincronizado + + Lento Nunca. Ou possivelmente quando o desempenho multithread não importa. Mas nunca seria melhor.
Bloqueio duplamente verificado + + Rápido Em casos raros, quando você precisa lidar com exceções ao criar o singleton (quando o singleton titular da classe não é aplicável)
titular da classe + + Rápido Sempre que multithreading for necessário e houver garantia de que o objeto singleton será criado sem problemas.

Prós e contras do padrão singleton

Em geral, um singleton faz exatamente o que se espera dele:
  1. Ele garante que sempre haverá apenas uma instância da classe.

  2. Ele fornece um único ponto de acesso global a essa instância.

No entanto, esse padrão tem deficiências:
  1. Um singleton viola o princípio de responsabilidade única: além de suas funções diretas, a classe singleton também controla o número de instâncias.

  2. A dependência de uma classe comum de um singleton não é visível no contrato público da classe.

  3. Variáveis ​​globais são ruins. Em última análise, um singleton se transforma em uma variável global pesada.

  4. A presença de um singleton reduz a capacidade de teste do aplicativo como um todo e das classes que usam o singleton em particular.

E é isso! :) Exploramos a classe Java Singleton com você. Agora, pelo resto da vida, ao conversar com seus amigos programadores, você pode mencionar não apenas o quão bom é o padrão, mas também algumas palavras sobre o que o torna ruim. Boa sorte em dominar este novo conhecimento.

Leitura adicional:

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION