(创建型模式)单例模式

76 阅读3分钟

前言

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。 在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

单例类型

  • 懒汉式:在真正需要使用对象时才去创建该单例类对象(时间换空间)
  • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用(空间换时间)

懒汉模式(线程不安全)

public class Singleton_01 {

    private static Singleton_01 instance;

    private Singleton_01(){

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

}

这种方式在并发情况下会创建出多个实例,从而没有达到单例的要求。

懒汉模式(线程安全)

public class Singleton_02 {

    private static Singleton_02 instance;

    private Singleton_02(){

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

}

这种方式虽然解决了线程安全问题,但是锁太重了会大大降低系统的性能。

饿汉模式(线程安全)

public class Singleton_03 {

    private static Singleton_03 instance = new Singleton_03();

    private Singleton_03(){

    }
    public static Singleton_03 getInstance(){
        return instance;
    }

}

饿汉模式是线程安全的,在系统启动时就会加载,后续有外部需要使用的时候获取即可。这样做的问题是即使不使用也会创建出来占用系统资源。

使用内部类(线程安全)

public class Singleton_04 {
    
    private Singleton_04(){
    }
    
    private static class SingletonHolder{
        private static Singleton_04 instance = new Singleton_04();
    }
    
    public static Singleton_04 getInstance(){
        return SingletonHolder.instance;
    }

}

使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的方式耗费性能。这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。

双重锁检查(线程安全)

public class Singleton_05 {

    private static volatile Singleton_05 instance;

    private Singleton_05(){
    }


    public static Singleton_05 getInstance(){
        if(null != instance){
            return instance;
        }
        synchronized (Singleton_05.class){
            if (null == instance){
                instance = new Singleton_05();
            }
        }
        return instance;
    }

}

双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。

单例枚举(线程安全)

public enum  Singleton_06 {

    INSTANCE;
    private DBConnection connection = null;
    private Singleton_06(){
        connection = new DBConnection();
    }
    public DBConnection getConnection(){
        return connection;
    }

}
public static void main(String[] args) {
    DBConnection connection = Singleton_06.INSTANCE.getConnection();
}

Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。也就是说,序列化的时候只将DATASOURCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。