Demonstração e compreensão do modo Java singleton

padrão singleton

Por que usar o padrão singleton? O padrão singleton garante que apenas um objeto de instância de uma classe exista na memória. Não importa quantas vezes o objeto de instância seja declarado, é o mesmo endereço na memória.
Não importa qual modo singleton privatize seu próprio construtor, ele impede que o exterior crie vários objetos instanciados diferentes por meio do construtor. O objeto é criado dentro da classe correspondente chamando seu próprio construtor e, em seguida, fornecido para fora na forma de um método estático.

1. Estilo chinês faminto

Quando a classe iniciar, o objeto instância criado será colocado na memória.

A ideia do estilo Hungry é 1. Tornar o construtor privado 2. Declarar uma 私有的静态的classe 类型的变量e atribuir um valor a ela por meio do construtor dentro da classe 3. Expor através do método estático
insira a descrição da imagem aqui
Ao usá-lo, você só precisa chamar a classe para fora O método estático fornecido pode
insira a descrição da imagem aqui

Mas esse tipo de modo singleton faminto tem uma desvantagem muito grande, ou seja, se houver muitas variáveis ​​de método em uma classe e não houver certeza se a instância é necessária, carregar diretamente o objeto na memória consumirá muito de memória.
insira a descrição da imagem aqui

2. Estilo preguiçoso

Somente quando esse objeto for usado, um objeto de instância de classe será criado e colocado na memória, o que pode impedir que objetos desnecessários sejam carregados na memória e economizar recursos. Comparado com o estilo chinês faminto, é uma 时间换空间maneira diferente.

Modo preguiçoso de pensar: 1. Torne o construtor privado 2. Declare uma 私有的静态的classe 类型的变量, não preste atenção a nenhuma atribuição 3. Ao fornecer um objeto de instância para fora, julgue se o objeto foi criado e, se não, crie um objeto para ele . exposição externa unificada

Comparado com o estilo faminto, o estilo homem preguiçoso cria o objeto quando o objeto é necessário (chamando esse método para fornecer o objeto exposto para o exterior) e, em seguida, julga se tal objeto já existe ao criar o objeto e continua a usar se existir , não existe um novo objeto como novo, garantindo que haja apenas um desses objetos na memória.

insira a descrição da imagem aqui
Mas também há um problema com esse estilo preguiçoso , ou seja, o thread não é seguro. Ao usar este objeto em um ambiente multiencadeado, podem ocorrer problemas de segurança de encadeamento devido à inconsistência de julgamento e operação

insira a descrição da imagem aqui

O motivo do problema de segurança de thread: o problema de segurança de thread só pode ocorrer quando é adquirido pela primeira vez e multiencadeado,
insira a descrição da imagem aqui
para que não haja problema de segurança de thread:
insira a descrição da imagem aqui

A solução específica é usar o estilo preguiçoso DSL ou o estilo preguiçoso de classe interna estática para resolver

3. Estilo de homem preguiçoso DSL (modo de homem preguiçoso de bloqueio duplo)

DSL: Double Check Lock
Pensando: Qual será o impacto se for adicionado diretamente ao método estático de obtenção do objeto synchronized? A operação de obtenção do objeto torna-se uma operação síncrona. A criação de um objeto pela primeira vez e o retorno de um objeto existente são operações síncronas. Sabe-se acima que os problemas de segurança de encadeamento podem ocorrer apenas em um ambiente multiencadeado quando o objeto é adquirido pela primeira vez. Todos os bloqueios são transformados em operações de thread único, o que afetará o desempenho.

insira a descrição da imagem aqui
Pensando: 为什么在变量User前使用volatile?
As três principais características do volátil:

  • visibilidade
  • atomicidade não garantida
  • Proibição de rearranjo de comando

A palavra-chave Volatile é adicionada antes da variável User, e suas 禁止指令重排características são usadas aqui. Então, o que é rearranjo de instrução? Você deve saber que, embora haja apenas uma linha de código em nosso próprio código user =new User(), ela pode ser executada em três etapas dentro da JVM: 1. Abra espaço na memória heap 2. Salve as informações do objeto no espaço aberto 3. Aponte para pela variável de objeto no espaço de memória da pilha Heap. No entanto, JVM tem a possibilidade de execução fora de ordem para fins de eficiência. Originalmente 1->2->3, 1->3->2 pode ocorrer após a execução fora de ordem. Embora o resultado final seja o mesmo, mas no código, quando houver vários threads para executá-lo, haverá uma pequena probabilidade de uma exceção.
insira a descrição da imagem aqui
Usar a modificação da palavra-chave volátil antes do usuário pode impedir o rearranjo das instruções de atribuição de variável do usuário e pode ser executado em a ordem de 1, 2 e 3 sem caos. Certifique-se de que a variável de objeto retornada tenha sido atribuída no espaço de heap

pensar:为什么在类中锁住的是user.class? synchronized 锁类与锁对象的区别?

  • O que sincronizado adiciona ao método não estático bloqueia o objeto e o que o método estático carrega bloqueia toda a classe. Por exemplo: eu Fcoloco a palavra chave sincronizar na frente de um método não estático, então seu escopo é somente para o mesmo objeto. Para o mesmo objeto, mesmo que esse método seja chamado em vários threads, apenas um thread pode inserir esse método ao mesmo tempo. Mas se eu criar dois objetos A, B e chamar esse Fmesmo método em três threads (threads T1, T2 usam o objeto A, thread T3 usa o objeto B), o que acontecerá?
    Ao mesmo tempo, no método F, o thread (T1 ou T2) pode chamar este método ao mesmo tempo que T3 e, ao mesmo tempo, apenas um thread no thread T1 e T2 está executando o método F.
    这就是对象锁,同一个时刻,在多线程中同一个对象只能有一个线程执行此方法

  • sincronizado é adicionado à classe bloqueada pelo método estático. Todos os objetos criados por esta classe podem ter apenas um objeto executando este método em multi-threading. Ainda no exemplo
    acima, se o método usa a classe bloqueada sincronizada, ao mesmo tempo , T1 , T2, T3, apenas um objeto de uma thread pode executar este método

A seguir estão seis testes para sincronizados: (O uso de suspensão aqui é para aumentar a probabilidade de problemas de segurança de encadeamento, o que não tem significado prático. Caso contrário, a execução simples do negócio é muito rápida e nenhuma outra oportunidade de inserção é fornecida)

  • Test 同一个object 不加任何锁, para testar problemas de segurança de thread em um ambiente de alta simultaneidade:
public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        TestSync testSync = new TestSync();
        new Thread(testSync::start,"A").start();
        new Thread(testSync::start,"B").start();
        new Thread(testSync::start,"C").start();
    }
}
class TestSync{
    
    
    void start(){
    
    
        System.out.println(Thread.currentThread().getName() + "begin");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    

        }
        System.out.println(Thread.currentThread().getName() + "end");
    }
}

resultado:
insira a descrição da imagem aqui
统一时刻,三个线程操作同一个对象进入方法


  • Teste 不同um objeto 不加任何锁para testar problemas de segurança de thread em um ambiente de alta simultaneidade:
    insira a descrição da imagem aqui
public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        TestSync testSync1 = new TestSync();
        TestSync testSync2 = new TestSync();
        TestSync testSync3 = new TestSync();
        new Thread(testSync1::start,"A").start();
        new Thread(testSync2::start,"B").start();
        new Thread(testSync3::start,"C").start();

    }
}
class TestSync{
    
    
    void start(){
    
    
        System.out.println(Thread.currentThread().getName() + "begin");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    

        }
        System.out.println(Thread.currentThread().getName() + "end");

    }
}
  • Test 同个object 加对象锁, para testar problemas de segurança de thread em um ambiente de alta simultaneidade:
public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        TestSync testSync = new TestSync();
        new Thread(testSync::start,"A").start();
        new Thread(testSync::start,"B").start();
        new Thread(testSync::start,"C").start();
    }
}
class TestSync{
    
    
   synchronized void start(){
    
    
        System.out.println(Thread.currentThread().getName() + "begin");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    

        }
        System.out.println(Thread.currentThread().getName() + "end");
    }
}

Pense: qual é o resultado?
Como o objeto está bloqueado e vários threads operam no mesmo objeto, ou seja, apenas um thread pode executar esse método ao mesmo tempo.
insira a descrição da imagem aqui

  • Test 不同个object 加对象锁, para testar problemas de segurança de thread em um ambiente de alta simultaneidade:
public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        TestSync testSync1 = new TestSync();
        TestSync testSync2 = new TestSync();
        TestSync testSync3 = new TestSync();
        new Thread(testSync1::start,"A").start();
        new Thread(testSync2::start,"B").start();
        new Thread(testSync3::start,"C").start();
    }
}
class TestSync{
    
    
  synchronized void start(){
    
    
        System.out.println(Thread.currentThread().getName() + "begin");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    

        }
        System.out.println(Thread.currentThread().getName() + "end");
    }
}

Pense: qual é o resultado?
Como o bloqueio de objeto é adicionado, mas cada objeto de encadeamento é diferente, vários encadeamentos podem inserir o método e o bloqueio de objeto é inválido
insira a descrição da imagem aqui

  • Test 同个object 加类锁, para testar problemas de segurança de thread em um ambiente de alta simultaneidade:
public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        TestSync testSync = new TestSync();
        new Thread(testSync::start,"A").start();
        new Thread(testSync::start,"B").start();
        new Thread(testSync::start,"C").start();

    }
}
class TestSync{
    
    
   void start(){
    
    
 	 synchronized (TestSync.class){
    
     // 在一个普通方法使用代码块的方式锁住一个类
        System.out.println(Thread.currentThread().getName() + "begin");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    

        }
        System.out.println(Thread.currentThread().getName() + "end");
		}
    }
}

Pense: qual é o resultado?
Cada objeto é bloqueado e apenas uma thread do objeto pode acessar este método ao mesmo tempo, então o resultado é o mesmo que o bloqueio do objeto
insira a descrição da imagem aqui

  • Test 不同个object 加类锁, para testar problemas de segurança de thread em um ambiente de alta simultaneidade:
public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        TestSync testSync1 = new TestSync();
        TestSync testSync2 = new TestSync();
        TestSync testSync3 = new TestSync();
        new Thread(testSync1::start,"A").start();
        new Thread(testSync2::start,"B").start();
        new Thread(testSync3::start,"C").start();

    }
}
class TestSync{
    
    
void start(){
    
    
 	 synchronized (TestSync.class){
    
     // 在一个普通方法使用代码块的方式锁住一个类
        System.out.println(Thread.currentThread().getName() + "begin");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    

        }
        System.out.println(Thread.currentThread().getName() + "end");
		}
    }
}

Pense: qual é o resultado?
Mesmo que existam vários objetos, apenas um thread do objeto pode acessar este método ao mesmo tempo
insira a descrição da imagem aqui

classe interna estática preguiçosa

Idéias: 1. Torne a estrutura privada 2. Crie uma classe interna estática 3. Declare a variável do tipo de objeto da classe externa nesta classe interna como o atributo da classe interna e use a estrutura privada da classe externa para atribuí-la ao variável de objeto de classe externa 4. Crie um método público e estático na classe externa e forneça o objeto de classe externa declarado pela classe interna estática no método. Observação
insira a descrição da imagem aqui
: o estilo preguiçoso da classe interna estática também é thread-safe

Pensando: 为什么静态内部类的方式单例模式是懒汉式?
Porque quando o programa iniciar, a JVM irá carregar a classe externa na memória para inicialização, mas a classe interna não será carregada e não será inicializada. Somente quando for usado, a classe será inicializada e suas variáveis ​​estáticas atribuídas. Em outras palavras, ele só será carregado na memória quando esse método de classe externa for chamado getInstance(), por isso é preguiçoso.

Pensando: 为什么静态内部类的方式是线程安全的呢?
O atributo estático da classe interna estática é atribuído pela JVM ao carregar esta classe, e a JVM irá bloqueá-lo ao inicializar esta classe, para que em um ambiente multiencadeado, a atribuição de variável estática da classe interna ainda seja discussão segura.

Pensando: Qual é a diferença entre o modo de classes internas estáticas e o modo de DSL? Ou quais são as desvantagens do modo singleton de construção de classe interna estática?
A maior desvantagem da classe interna estática é que devido ao uso de atribuição de variável estática, os parâmetros não podem ser passados ​​de fora
.

public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        new Thread(()->{
            System.out.println(User.getInstance(1).getAge());
        },"A").start();
        new Thread(()->{
            System.out.println(User.getInstance(2).getAge());
        },"B").start();
        new Thread(()->{
            System.out.println(User.getInstance(3).getAge());
        },"C").start();
    }
}
class User{
    
    
    private static User user;
    private int age;
    private User(){
    
    

    }
    public int getAge(){
    
    
        return this.age;
    }
    private User(int age){
    
    
        this.age=age;
    }
    public static User getInstance(int age){
    
    
        if(user==null){
    
    
            synchronized (User.class){
    
    
                if(user==null){
    
    
                    System.out.println(Thread.currentThread().getName()+"先进来");
                    user=new User(age);
                }
            }
        }

        return user;
    }
}

Como destruir o padrão singleton

Como o núcleo do padrão singleton é a privatização do construtor, se o construtor da classe for definido como público por meio de reflexão, vários objetos de instância diferentes não podem ser instanciados por meio do construtor?


Fase 1:
Ideia: Obtenha os parâmetros de construção através da reflexão de classe, e defina os parâmetros de construção para serem visíveis (porque são privados), e então crie novos objetos através da construção obtida através da reflexão. Dessa forma, cada objeto é obtido por meio do construtor e existem várias instanciações de objetos.

    System.out.println(User.getInstance());// 使用单例模式的构建对象
        Constructor<User> constructor = User.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        System.out.println(constructor.newInstance()); // 使用反射获取的对象构建对象
        System.out.println(constructor.newInstance()); // 使用反射获取的对象构建对象

resultado
insira a descrição da imagem aqui

Como evitar tais rachaduras? Adote a abordagem do estágio 2


Estágio 2:
Ideia: Ao construir, julgue se tais objetos existem.
insira a descrição da imagem aqui
Mas isso só se aplica a objetos que foram adquiridos usando um singleton antes de usar a reflexão para adquirir objetos. Se todos eles forem adquiridos usando a construção de reflexão, o construtor da classe não poderá adquirir objetos que não sejam criados usando o modo singleton , tornando esse método inválido.
Fase 3:
Ideia: Seja um modo de coluna única ou a criação de um objeto por reflexão, o método de construção é inseparável. Para perceber que existe apenas uma instância, você só precisa garantir que o método de construção possa ser chamado apenas uma vez. Um sinalizador pode ser definido para garantir que o construtor seja chamado apenas uma vez.
Mas ainda há um problema com isso, ou seja, modificar esse sinalizador por meio da reflexão. Se o sinalizador não for do tipo booleano, você sabe por que ele precisa ser modificado por meio de retrorreflexão? Se for definido como uma senha, o método de construção pode ser chamado para instanciar um objeto somente se ele corresponder à senha e pode ser alterado para outro valor imediatamente após a chamada uma vez, de modo que apenas alterando o sinalizador para ser o mesmo que a senha através da reflexão pode outro objeto ser instanciado.
Como o modo de instância única não pode ser destruído pela reflexão? O padrão singleton pode ser construído por enumeração.

4. Use a classe de enumeração

public class Application {
    
    

    public static void main(String[] args) throws Exception {
    
    
        System.out.println(Singleton.INSTANCE.getInstance());
        System.out.println(Singleton.INSTANCE.getInstance());
    }
}

enum Singleton {
    
    

    INSTANCE;
    private User instance;

    Singleton() {
    
    
        instance = new User();
    }

    private class User{
    
    

    }

    public User getInstance() {
    
    
        return instance;
    }
}

Acho que você gosta

Origin blog.csdn.net/m0_52889702/article/details/128821533
Recomendado
Clasificación