设计模式之单例模式

小明请假了,直入主题

单例模式,望文生义,就是只有一个实例的类,我们真的需要这种只有一个实例的类吗?答案的肯定的,在平时的程序中,单例也是我们经常会遇见和使用的,现在我们就来深入的认识一下单例模式。

用途

我们都知道单例模式的用途很多,那么具体哪些情况下会使用它呢?现在我们就来总结一下:线程池、缓存、对话框、处理偏好设置和注册表对象、日志对象、充当打印机、显卡等设备的驱动程序对象等等……在这些情况下,一般我们不需要也不允许存在多个对象,如果不这样,可能会给我们带来一些问题。单例模式我们一定要清楚一点,就是在程序中有且只有一个类的实例。

如何确保只有一个实例

我们都知道,实例化对象的方式是通过new的方式,那么其实我们可以在很多地方通过new的方式生成一个类的实例,如果只能让类有一个实例,我们就必须去屏蔽这种做法,让这个类不能通过这种方式去实例化,那么可以这样做吗?好消息是:在Java中,我们可以申明一个私有的构造方法,像这样

public MyClass {
    private MyClass(){
    }
} 

这样,我们的类就不能被实例化了,那么新的问题来了,我们怎么实例化那唯一的一个实例呢?在类的外部我们不能访问这个类的构造函数,但是在类的内部我们可以访问啊,但是这个类没有实例化,我们不能访问它啊!这不就成了先有蛋还是先有鸡的问题了么,其实不然,我们不能用它的实例对象名,我们可以访问它的类名啊,在Java中可以通过类名去访问一个静态的方法,在这个方法中我们就可以实例化这个唯一个的对象:

public MyClass {
    private MyClass(){
    }

    public static MyClass getInstance(){
        return new MyClass();
    }
}

似乎还有一些问题,我们并没有确保唯一性,所以稍微修改一下:

方法一(懒汉式):

public Singleton {

    private static Singleton instance;

    private Singleton(){
    }

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

代码很容易理解,在单线程中,我们要获得这个类的实例,只能通过Singleton.getInstance()方法去得到,而且返回的实例永远都是同一个(第一次会先实例化)。那么这就是一个单例模式的实现。

定义

单例模式确保只有一个实例,并提供一个全局访问点。

优点

  1. 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例
  2. 因为类控制了实例化过程,所以类可以灵活更改实例化过程

缺点

  1. 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题

还有更重要的事

我们可以看到,单例模式的缺点很少,这也是因为他足够简单(但是作用很大),那么先解决上面缺点说的解决每次都检查实例是否存在的问题,解决他的方式也是实现单例模式的一种很重要的方式之一:饿汉式

public class Singleton{
    private static final Singleton instance = new Singleton();
    private Singleton(){
    }
    public static Singleton getInstance(){
        return instance;
    }
}

这样,在类被加载时,我们需要的那个唯一的实例就存在了,以后的每一次访问都是那同一个实例。但是这样的就完美了吗?在某些情况下,它是完美的,就是我们的这个实例所占的资源很少,并且在程序的生命周期里,它是一定会被使用的。这样,我们就可以使用这种方式,否则这种方式可能会造成一些资源的浪费。那么如果有那种占用资源很多,还可能不会被使用的单例,我们怎么去设计呢?很容易想到,在我们真正使用到它的时候才去实例化它,就如我们上面的懒汉式。但在大多数情况下,我们的程序是多线程的,所以使用上面的方法一会出现一个问题,因为我们的new不是原子操作,所以在多线程情况下,还是会出现多实例化的情况。我们知道在多线程情况下,为了防止某个资源同时被多个线程访问,我们需要给它加锁进行同步:

public class SingletonClass{
    private static SingletonClass instance;
    public static synchronized SingletonClass getInstance(){
        if(instance==null){
            instance=new SingletonClass();
        }
        return instance;
    }
    private SingletonClass(){
    }
}

这种方式防止了在多线程情况下会出现的问题,并且是在需要的时候才去实例化它。但是它完美了吗?同样的,在某些情况下它是完美的。因为加锁同步的方式会比不加锁的方式更低效,而且懒汉式的单例模式每次访问单例时都会加锁再释放锁,所以会失去一些效率。所以,懒汉式的方式在我们对效率要求不那么严苛的情况下,它是完美的。那如果我们对效率有要求,单例又很占用资源,还可能不会经常使用到它,那么在这种情况下有完美的方式吗?答案是肯定的,那就是双重检查加锁:

public class SingletonClass{
    private volatile static SingletonClass instance;
    public static SingletonClass getInstance(){
        if(instance == null){
            synchronized(SingletonClass.class){
                if(instance == null){
                    instance=new SingletonClass();
                }
            }
        }
        return instance;
    }
    private SingletonClass(){
    }
}

如果对性能的要求极高,而且单例被访问的频率很高,双重检查加锁的方式是可取的,否则这种方式可能会是杀鸡用了牛刀。那么这种方式是怎么避免效率的损失呢,可以看到,我们不是每一次都对资源进行了加锁,而是当知道单例对象为空时再加锁,这样就只会在第一次会出现加锁,不会有效率问题(同样会有每一次的为空判断)。那么volatile的作用呢?模拟一下,ThreadOne到第一个为空判断时,发现instance为空,这时候系统将资源给ThreadTwo,由于ThreadOne还没来得及对单例实例化,所以ThreadTwo任然判断instance为空,所以会继续往下实例化instance,实例化完成以后,ThreadOne继续往下走……现在明白了吧,如果没有volatile的修饰,在ThreadOne中的instance还是为空,有了volatile,会更新ThreadOne中的instance,这样再第二次判断为空时,instance已经不再为空了,就不会再实例化了。明白刚刚说的为什么这是牛刀了吧!

单例的新鲜实现化

在Android中,我们会经常使用一个类就是LayoutInflater,用它将我们的布局文件转化成具体的View控件,其实在程序中我们的LayoutInflater就是一个单例,我们看看它如何实现:

LayoutInflater.java中from方法

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater = (LayoutInflater)context
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

最终会调用到Contextimpl.java中的

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

然后是SystemServiceRegistry中的getSystemService(ContextImpl ctx, String name)方法,这个方法是这样的

private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =new HashMap<String, ServiceFetcher<?>>();

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

private static <T> void registerService(String serviceName, Class<T> serviceClass,ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

可以看到我们的对象是从一个map中通过key的方式取出来的,而单例的对象又是通过registerService(String serviceName, Class serviceClass, ServiceFetcher serviceFetcher)方法将服务类单例添加到map中的。这也是一种Android中单例模式的使用。

坚持原创分享,您的支持将鼓励我不断前行!