设计模式之单例模式

来源:互联网 发布:广西广电网络机顶盒 编辑:程序博客网 时间:2024/05/16 19:29

单例模式是应用最广的设计模式之一。为什么会使用单例模式呢?许多时候我们整个系统只允许有一个全局对象,然后用这个对象来协调系统整体的行为;或者我们要实例化的类需要的资源较多,耗费时间比较久,那么我们也可以使用单例模式在整个程序运行期间维持一个唯一的实例对象。

单例模式的要求

  • 构造函数私有化,即访问修饰符为“private”

  • 通过一个静态方法或者枚举返回单例类对象

  • 要确保单例类的对象有且只有一个,并保证线程安全

  • 确保单例类实例对象在反序列化的过程中不会生成新的对象

单例模式实现

饿汉模式

public class Singleton {    //饿汉模式,首先实例化对象实例    private static Singleton mSingleton = new Singleton();    private Singleton() {    }    public static synchronized Singleton getInstance() {        return mSingleton;    }}

懒汉模式

public class Singleton {    private static Singleton mSingleton;    private Singleton() {    }    public static synchronized Singleton getInstance() {        //懒汉模式,首先判断是否对象为空        if (mSingleton == null) {            mSingleton = new Singleton();        }        return mSingleton;    }}

DCL模式(Double Check Lock)

public class Singleton {    //volatile避免虚拟机指令的乱序执行    private volatile static Singleton mSingleton;    private Singleton() {    }    public static Singleton getInstance() {        //第一次判断:避免不必要的同步开销        //第二次判断:检测mSingleton是否为空,为空则进行实例化        if (mSingleton == null) {            synchronized (Singleton.class) {                if (mSingleton == null) {                    mSingleton = new Singleton();                }            }        }        return mSingleton;    }}

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

需要注意的是,由于java虚拟机存在指令乱序执行,所以需要使用关键字“volatile”来修饰mSingleton变量,以此避免DCL失效。

volatile 关键字作用大致分为两点:
1. 保证修饰的变量在各个线程中保持可见性(可以理解为实时更新)
2. 可避免虚拟机指令的乱序执行优化

静态内部类模式

public class Singleton {    private Singleton() {    }    public static Singleton getInstance() {        return SingletonHolder.mSingleton;    }    private static class SingletonHolder {        private static final Singleton mSingleton = new Singleton();    }} 

第一次加载Singleton类的时候并不会初始化mSingleton,只有第一次在调用getInstance方法的时候才会初始化。所以当第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,并且实例化final修饰的mSingleton对象,从而保证了线程安全。

以上四种方式还缺乏考虑一种情况:如何避免被反序列化。如果我们将单例类的唯一变量通过序列化保存下来(例如写到磁盘),然后通过反序列化就可以再次得到另外一个新的实例对象。明显这样的结果我们不能让他发生。我们只需要去加入一个方法即可:

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

反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化的方法readResolve(),这个方法可以让开发人员控制对象的反序列化。如果我们要杜绝单例对象在被反序列化的时候重新生成对象,那么必须要加入readResolve方法,在该方法中将mSingleton变量返回即可,而不是默认的去生成一个新的实例对象。

以上就是几种单例模式的实现细节,核心就是首先保证类的构造函数私有化;其次提供一个static修饰的方法返回该类的实例化对象;最后要考虑到线程安全以及反序列化。

原创粉丝点击