设计模式---单例模式

1)简介

单例设计模式是一种创建设计模式,单例类只能有一个实例存在。
核心步骤:私有构造,自建实例,提供访问方法

2)实现代码
a.饿汉式
public class Singleton{
    //自建实例,注意是静态变量以确保唯一,私有
    private static Singleton instance = new Singleton();
    //私有构造,防止其他地方创建对象
    private Singleton(){}
    //提供访问单例的方法
    public Singleton getInstance(){
        return instance;
    }
}
饿汉式优点是一加载类就创建实例,避免了线程安全问题,缺点是单例实例可能很久不使用,一直存在于内存有些浪费资源(不过现在内存貌似比较廉价,可以忽略这点吧)

b.懒汉式
public class Singleton{
    //声明变量,暂时不创建实例
    private static Singleton instance;
    //私有构造,防止其他地方创建对象
    private Singleton(){}
    //提供访问单例的方法
    public Singleton getInstance(){
        //判断是否为空,根据结果创建实例
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
这种懒汉式虽然节约了资源,但是存在线程安全问题:即一个线程判断为空后失去CPU执行权限,另一个线程又判断为空,两个线程最终都创建了一个实例。

懒汉式同步方法模式:
public class Singleton{
    //声明变量,暂时不创建实例
    private static Singleton instance;
    //私有构造,防止其他地方创建对象
    private Singleton(){}
    //提供访问单例的方法
    public synchronized Singleton getInstance(){
        //判断是否为空,根据结果创建实例
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
这种懒汉式当getInstance方法调用的次数少时可以,调用多的时候因为对整个方法都进行加锁,会影响性能。

懒汉式双检锁模式(DCL double-checked locking):
public class Singleton{
    //声明变量,暂时不创建实例
    private static Singleton instance;
    //私有构造,防止其他地方创建对象
    private Singleton(){}
    //提供访问单例的方法
    public Singleton getInstance(){
        //判断是否为空,根据结果创建实例
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
这种懒汉式即线程安全,又不会因为同步创建对象影响性能
但是,如果单例对象需要进行序列化和反序列化操作,单例模式会被破坏掉,原因稍后说明

c.登记模式/静态内部类
public class Singleton{
    //静态内部类持有实例
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    //私有构造
    private Singleton(){}
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}
静态内部类模式利用内部类加载时创建单例实例,避免了线程安全问题,且类加载是在主动调用的时候加载,因此又具备lazy初始化的功能,另外单例实例是静态内部类的属性,反序列化时虽然通过反射创建了单例类的实例,但是通过getInstance获取实例的时候依然是原来的实例。

d、枚举
public enum Singleton{
    INSTANCE;
}

3)返序列化破坏单例的说明
序列化与反序列化代码:
public static void main(String[] args) throws IOException, ClassNotFoundException {
    //Write Obj to file
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
    oos.writeObject(Singleton.getSingleton());
    //Read Obj from file
    File file = new File("test");
    ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
    Singleton newInstance = (Singleton) ois.readObject();
    //判断是否是同一个对象
    System.out.println(newInstance == Singleton.getSingleton());
}
输出结果为false
ObjectInputStream类的readObject 读取对象时返回的对象是调用 readOrdinaryObject 返回的对象,
readOrdinaryObject 方法有下面一段代码,返回的是其中的obj
Object obj;
try {
    obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
    throw (IOException) new InvalidClassException(
        desc.forClass().getName(),
        "unable to create instance").initCause(ex);
}
isInstantiable 方法为校验是否可以序列化,如果可以返回true,我们需要序列化,肯定返回true
故返回的对象实际为desc.newInstance()返回的对象,而desc为Class对象,显然最终返回的是通过反射新建的一个实例,已经不是原来的实例了。
posted @ 2020-01-03 09:09  守望一心  阅读(135)  评论(0编辑  收藏  举报