设计模式 之 单例模式

来源:互联网 发布:做网络系统集成的公司 编辑:程序博客网 时间:2024/05/04 05:54

记录一下学习的设计模式,方便日后查看

单例模式

单例模式是确保某一个类只有一个实例,自行实例化并向整个系统提供这个实例。应用场景:比如创建一个对象需要消耗许多资源,访问IO或数据库等,此时需要考虑单例模式。

实现单例模式的几个关键点:
(1) 构造函数不对外开放,一般为private
(2) 构造一个静态方法或者枚举返回实例对象
(3) 确保单例类的对象只有一个,尤其在多线程环境下
(4) 确保单例类对象在反序列化的时候不会重新构建对象

几种常见的单例实现
一. 饿汉模式

public class EHan {    private static final EHan mEHan = new EHan();    // 私有构造函数    private EHan() {    }    // 共有静态函数,对外暴露获取单例对象    public static EHan getInstance() {        return mEHan;    }}

由代码可知,饿汉模式顾名思义,就是一直很饿,时时刻刻在吃内存(我的个人理解)。一开始就创建好对象,节省了时间,但消耗内存。饿汉模式是线程安全的。

二. 懒汉模式

public class LanHan {    private static mLanHan = null;    // 私有构造函数    private LanHan() {    }    // 共有静态函数,获取单例对象    public static synchronized LanHan getInstance() {        if (mLanHan == null) {            mLanHan = new LanHan();        }        return mLanHan;    }} 

懒汉模式,顾名思义,就是比较懒,用到才初始化,这在一定程度上节约了内存资源,但是时间反应稍慢。这里要增加同步锁synchronized,保证在多线程环境下是安全的。懒汉模式的主要的问题不只是在第一次用到时才初始化导致的反应慢,更大的问题是每次调用getInstance的时候都会进行同步,会造成不必要的同步开销,故不建议这样用。

三. Double CheckLock(DCL)模式
DCL其实是对懒汉模式的优化,在getInstance时加了一层判断,避免每次调用都进行同步操作。

public class DCL {    private static sInstance = null;    // 私有构造函数    private DCL() {    }    // 共有静态函数,获取单例对象    public static DCL getInstance() {        // 判断实例是否为空,不为空就直接返回,避免同步操作        if (sInstance == null) {            synchnoized(DCL.class) {                if(sInstance == null) {                    sInstance = new DCL();                }            }        }        return sInstance;    }}

DCL模式同样存在第一次加载反应耗时的问题,但是避免了每次加载都会同步的耗时操作,资源利用率高。DCL模式能够满足一般的开发需求,但在特殊环境下也会出问题,下面来分析一下:
假设线程A执行getInstance方法,执行到sInstance = new DCL ( ); 这句话会最终被编译成多条汇编指令,大致做了3件事:
(1) 给DCL实例分配一个内存
(2) 调用DCL( )构造函数,初始化成员字段
(3) 将mInstance对象指向分配的内存空间(此时sInsatnce已经不是null)
由于Java编译器允许处理器乱序执行,2、3的执行顺序无法保证,在高并发的情况下会出现,线程A执行3但尚未执行2的时候此时线程B调用该函数,此时sInstance已经非空,会被直接取走,但由于2未执行,使用时会报错。当然这种能够情况发生概率很小。
JDK1.5以后,官方做了修复,调整JMM(Java Memery Model)、引入volatile关键字。因此,将sInstance的定义改成

    private volatile static DCL sInstance = null;

就可以保证sInsatnce对象每次都是从主内存中读取,DCL的写法不出现错误。

三. 静态内部类单例模式
DCL的实现方法会面临上述所讲的双重检查锁定失效的情况,建议使用如下代码:

public class Singleton {    private Singleton() { }    public static Singleton getInstance() {        return SingletonHolder.sInstance;    }    /**     * 静态内部类     */    private static class SingletonHolder {        private static final Singleton sInstance = new Singleton();    }}

第一次加载Singleton类的时候并不会初始化sInstance,只有在第一次调用getInstance的时候才会导致sInsatnce被初始化。这种方法不仅保证线程安全,也能够保证单例对象的唯一性。所以推荐使用这种单例实现方式。

四. 枚举实现单例
上述几种实现方式在一种情况在会出现重新创建对象的情况,那就是反序列化。如何防止反序列化时重新创建对象,主要有两种方案,第一种方案就是采用枚举实现单例。

public enum SingletonEnum {    INSTANCE;    public void doSomething() {        System.out.println("do sth.");    }}

第二种方案就是要在类中加入如下方法:

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

在readResolve方法中将sInstance对象返回。枚举则不用。

以上是单例模式常用的实现方法,推荐使用静态内部类实现方案,由于DCL方案是在高并发等极端复杂环境下才有可能出现问题,可以满足绝大多数的开发需求,故也经常被使用。

0 0