单例模式

2020-01-10  本文已影响0人  milovetingting

个人博客

http://www.milovetingting.cn

单例模式

模式介绍

整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。

定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

实现单例模式主要有如下几个关键点:

单例模式的写法

在声明静态对象时已经初始化。

public class Singleton {

    private static final Singleton mInstance = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return mInstance;
    }

}

懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化。优点是:单例只在使用时才会被实例化。缺点是:第一次加载需要及时进行实例化,反应稍慢,每次调用时都进行同步,造成不必要的同步开销。这种模式不建议使用。

public class Singleton {

    private static volatile Singleton mInstance;

    private Singleton() {

    }

    public static synchronized Singleton getInstance() {
        if (mInstance == null) {
            mInstance = new Singleton();
        }
        return mInstance;
    }

}

DCL模式实现单例的优点是既能够在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。

public class Singleton {

    private static volatile Singleton mInstance;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (mInstance == null) {
            synchronized (Singleton.class) {
                if (mInstance == null) {
                    mInstance = new Singleton();
                }
            }
        }
        return mInstance;
    }

}

当第一次加载Singleton类时并不会初始化sInstance,只有在第一次调用Sington的getInstance方法时才会导致sInstance被初始化。这是推荐使用的单例模式实现方式。

public class Singleton {

    private Singleton() {

    }

    public static Singleton getInstance() {
        return SingletonHolder.sInstance;
    }

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

}

在上述的几种单例模式实现中,在反序列化的情况下,它们就会出现重新创建对象。

序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效地获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建一个新的实例。反序列化操作提供了一个特别的钩子函数,类中具有一个私有的readResolve()函数,这个函数可以让开发人员控制对象的反序列化。如果要杜绝对象在反序列化时重新生成对象,必须加入readResolve函数。而枚举则不存在这个问题。

private Object readResolve() throws ObjectStreamException {
        return mInstance;
    }

对于序列化,有两点需要注意:

  1. 可序列化类中的字段类型不是Java的内置类型,那么该字段也需要实现Serializable接口

  2. 如果调整了可序列化类的内部结构,如新增,去除某个字段,但没有修改serialVersionUID,那么会引发java.io.InvalidClassException异常或者导致某个属性为0或者null。此时的最好方案是直接将serialVersionUID设置为0L,这样即使修改类的内部结构,反序列化也不会报错,只是新修改的字段会为0或者null。

写法简单是枚举单例的最大优点。枚举在Java中与普通的类是一样的,不仅能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。

public enum Singleton {

    INSTANCE;
    
    public void doSomething()
    {
        
    }

}
public class SingletonManager<T> {

    private static Map<String, Object> objMap = new HashMap<>();

    private SingletonManager() {

    }

    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objMap.get(key);
    }

}

在程序的初始阶段,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏具体实现,降低耦合度。

上一篇 下一篇

猜你喜欢

热点阅读