【设计模式】单例模式

所谓的单例模式指的是一个类只允许产生一个实例化对象。

常见的单例模式一般又分为:懒汉式和饿汉式。

单例模式有如下特点:

  1. 只能有一个实例化对象;
  2. 这个实例化对象必须是自己创建自己的唯一实例
  3. 这个实例化对象必须为所有其他对象提供

 

饿汉式单例

饿汉式单例,在类初始化时,已经自行实例化,简单来说就是一上来就new

public class Singleton{

    //在类的内部可以访问私有结构,所以可以在类的内部产生实例化对象
    //一上来就new
    private final static Singleton INSTANCE= new Singleton();
    
    //private声明构造
    private Singleton(){}

    public Singleton getInstance(){
        return INSTANCE;
    }
}

不管你是否使用Singleton类的对象,只要该类加载了,那么一定会自动创建 好一个公共的instance对象。既然是饿汉式,就希望整体的操作之中只能够有一个实例化对象,所以一般还会在前 面追加一个final关键字。

 

懒汉式单例

懒汉式单例只在第一次调用的时候实例化自己,简单来说就是用的时候才会new

懒汉式单例也有多种实现方式,这里只说三种线程安全的实现方式,静态内部类、双重检查锁定、枚举类

1、静态内部类(极力推荐)

//延迟加载、线程安全
public class Singleton{

    private Singleton(){}

    //静态内部类
    //静态类在使用的时候才会加载,而且只加载一次
    private static class T{
        private static Singleton INSTANCE = new Singleton();
    }

    private static Singleton getInstance(){
        return T.INSTANCE;
    }
}

2、DCL(双重检查锁定)

public class Singleton{
    
    public 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;
    }

}

解析

  1. 使用私有的构造函数,确保正常情况下该类不能被外部初始化(非正常情况比如通过反射初始化,一般使用反射之后单例模式也就失去效果了)。
  2. getInstance方法中第一个判空条件,逻辑上是可以去除的,去除之后并不影响单例的正确性,但是去除之后效率低。因为去掉之后,不管instance是否已经初始化,都会进行synchronized操作,而synchronized是一个重操作消耗性能。加上之后,如果已经初始化直接返回结果,不会进行synchronized操作。
  3. 加上synchronized是为了防止多个线程同时调用getInstance方法时,各初始化instance一遍的并发问题。
  4. getInstance方法中的第二个判空条件是不可以去除,如果去除了,并且刚好有两个线程a和b都通过了第一个判空条件。此时假设a先获得锁,进入synchronized的代码块,初始化instance,a释放锁。接着b获得锁,进入synchronized的代码块,也直接初始化instance,instance被初始化多遍不符合单例模式的要求。加上第二个判空条件之后,b获得锁进入synchronized的代码块,此时instance不为空,不执行初始化操作。
  5. instance的声明有一个voliate关键字,如果不用该关键字,有可能会出现异常。因为instance = new Test();并不是一个原子操作,会被编译成三条指令,如下所示。 
    1. 给Test的实例分配内存
    2. 初始化Test的构造器
    3. 将instance对象指向分配的内存空间(注意 此时instance就不为空) 

然后,java会指令重排序,JVM根据处理器的特性,充分利用多级缓存,多核等进行适当的指令重排序,使程序在保证业务运行的同时,充分利用CPU的执行特点,最大的发挥机器的性能!简单来说就是jvm执行上面三条指令的时候,不一定是1-2-3这样执行,有可能是1-3-2这样执行。如果jvm是按照1-3-2来执行的话,当1-3执行完2还没执行的时候,如果另外一个线程调用getInstance(),因为3执行了此时instance不为空,直接返回instance。问题是2还没执行,此时instance相当于什么都没有,肯定是有问题的。然后咧,voliate有一个特性就是禁止指令重排序,上面的三条指令是按照1-2-3执行的,这样就没有问题了。

3、枚举类

//线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用
public enum Singleton{

    INSTANCE; //枚举元素本身就是单例

}

 

单例模式的应用场景

数据库连接池、多线程的线程池。Windows的任务管理器就是很典型的单例模式。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值