单例模式

有时候又称“单件模式”或者“单态模式”,是Java中比较常见的创建型设计模式,他的最主要的目的是保证一个类只有一个实列,并提供一个访问它的全局访问点。比较经典的例子就是Windows系统中的“回收站”。

主要适用情况:

  1. 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  2. 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

单例模式具有以下几个特征:

  • 单例类只能有一个实例;
  • 单例类必须自己创建自己的唯一实例;
  • 单例类必须给所有其他对象提供这一实例;

那么怎么确保只能有一个实例?这其实就是单例模式实现的核心,主要实现思路是:

  1. 私有化构造方法,防止外界创建该类的对象。
  2. 提供静态实例和获取实例的静态方法,向外部对象提供单例类对象。

单例模式实现

常见的单例模式实现方式:

饿汉式”实现方式:

public class Singleton {    // 在自己内部定义自己的一个实例,只供内部调用    private static final Singleton singleton = new Singleton();        ///私有化构造函数,避免外界创建该类的对象    private Singleton() {    }        // 这里提供了一个供外部访问本class的静态方法,可以直接访问    public static Singleton getInstance() {        return singleton;    }}

另一种“饿汉式”实现方式:

public class Singleton {    // 在自己内部定义自己的一个实例,只供内部调用    private static final Singleton singleton;    static {        singleton = new Singleton();    }    ///私有化构造函数,避免外界创建该类的对象    private Singleton() {    }    // 这里提供了一个供外部访问本class的静态方法,可以直接访问    public static Singleton getInstance() {        return singleton;    }}

“饿汉式”单例基于classloder机制避免了多线程的同步问题,不过,singleton在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

懒汉式”实现方式:

public class Singleton {    // 在自己内部定义自己的一个实例,只供内部调用    private static Singleton singleton ;        ///私有化构造函数,避免外界创建该类的对象    private Singleton() {    }    // 这里提供了一个供外部访问本class的静态方法,可以直接访问    public static Singleton getInstance() {        if (null == singleton)            singleton = new Singleton();        return singleton;    }    }

上面这种“懒汉式”单例在多线程的情况下可能会出现问题,想象下,如果两个线程同时调用getInstance()方法,都出现singleton为空,因此可能出现多个单例实例对象,违背了单例原则,为了防止这种情况,一般都是通过添加同步锁来解决这样的问题

通常加锁情况:

1.将方法进行同步

public class Singleton {    // 在自己内部定义自己的一个实例,只供内部调用    private static Singleton singleton ;    ///私有化构造函数,避免外界创建该类的对象    private Singleton() {    }    // 这里提供了一个供外部访问本class的静态方法,可以直接访问    public static synchronized  Singleton getInstance() {        if (null == singleton)            singleton = new Singleton();        return singleton;    }}

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。每次获取单例对象的时候都需要同步,如果多个线程同时获取单例对象,将需要花比较长的时间,其实真正需要同步的是单例对象实第一次例化时需要同步,一旦实例化后就不需要同步了。

2.将对象进行同步

public class Singleton {    // 在自己内部定义自己的一个实例,只供内部调用    private volatile static Singleton singleton;    ///私有化构造函数,避免外界创建该类的对象    private Singleton() {    }    // 这里提供了一个供外部访问本class的静态方法,可以直接访问    public static Singleton getInstance() {        if (null == singleton)            synchronized (Singleton.class) {                //同步锁后,再进行一次singleton对象的判断                if (null == singleton) {                    //如果单例类的对象为空,则实例化单例类的对象                    singleton = new Singleton();                }            }        return singleton;    }}

这也是比较常见的一种加锁模式,常称作“双重锁的形式”。上面这些单例模式在反射时,使用多个类加载器时,可能倒是单例失效,产生多个实例,因此可以将创建实例过程对外部隔离

3.静态内部类单例模式

public final class Singleton {    ///私有化构造函数,避免外界创建该类的对象    private Singleton() {    }    // 这里提供了一个供外部访问本class的静态方法,可以直接访问    public static Singleton getInstance() {        return SingletonHolder.mSington;    }    private static class SingletonHolder{        private static final Singleton mSington = new Singleton();    }}

第一次加载Singleton类时不会去初始化mSington,只有第一次调用getInstance(),虚拟机加载SingletonHolder并初始化mSington,这样不仅能确保线程安全,也能保证Singleton类的唯一性。所以,推荐使用静态内部类单例模式。

简单总结

  • 单例模式确保程序中一个类最多只有一个实例;
  • 单例模式也提供访问这个实例的全局点;
  • 在Java中实现单例模式需要私有化构造器、一个静态方法和一个静态变量;
  • 确定在性能和资源上的限制,然后小心的选择适当的方案来实现单例,以解决多线程的问题;
  • 如果使用多个类加载器,可能导致单例失效而产生多个实例