设计模式——小谈单例

来源:互联网 发布:linux 复制命令 编辑:程序博客网 时间:2024/06/14 07:23

小谈单例模式

一、引子

不出意外,谈到设计模式,大家第一个想到的应该会是单例模式,因为这是应用最广的设计模式之一,并且也相对简单一点。


在诸如使用线程池、缓存、网络连接、访问数据库以及文件(IO)读写等情景下,产生一个对象需要消耗大量的资源。

而我们都知道,单例模式要求一个单例类只能保证有一个实例化对象。

因此,在类似的一些情景下,使用单例能有效的减少资源的消耗,提高效率。

So,就让我们一起来看看单例模式吧。


二、正文

1、常见的单例的特点

1.1、构造方法私有化,即采用private修饰
1.2、一般通过暴露一个静态的方法返回单例类的对象
1.3、确保单例类只有一个对象(特别注意多线程情况下)
1.4、确保在【反序列化】时,不会重新构建对象

一般情况下,单例类的特点可用上述四点说明。


2、分门别类看单例

2.1、饿汉式

饿汉式是最简单的单例类实现方式。我们常见的有关单例类的代码描述,一般情况下就是饿汉式。具有帮助我们理解上述四大特点的作用,但在实际工程中,并不常用。在此贴出举例代码如下:

public class SingletonDemo1{    private static SingletonDemo1 mSingleInstance = new SingletonDemo1();    //私有化构造方法    private SingletonDemo1(){    }    //提供公有的静态方法,暴露接口供外部调用获取单例类    public static getSingleInstance(){        return mSingleInstance;    }}

【☆☆☆】可以看到:饿汉式中,静态对象在声明时已经完成了初始化操作。

2.2、懒汉式

由于饿汉式不具备实际上的应用效果,那么我们再来看一种更进一步的单例类写法—懒汉式。

public class SingletonDemo2{    private static SingletonDemo2 mSingleInstance;    //私有化构造方法    private SingletonDemo2(){    }    //提供公有的静态方法,暴露接口供外部调用获取单例类    public static synchronized SingletonDemo2 getSingleInstance(){        if (mSingleInstance == null) {            mSingleInstance = new SingletonDemo2();        }        return mSingleInstance;    }}

很明显,这里的实例对象是在共有的方法被调用时才实例化。与此同时,添加了【synchronized】关键字来修饰,能保证在多线程情况下,单例对象的唯一性。


但是,有一点需要注意:假设mSingleInstance已经被实例化,但是每次调用相应的方法都会进行【同步】,这导致了资源的浪费。

因此,懒汉式也不推荐日常使用。

2.3、静态内部类单例模式

public class SingletonDemo3{    //私有化构造方法    private SingletonDemo3(){}    //提供公有的静态方法,暴露接口供外部调用获取单例类    public static SingletonDemo3 getSingleInstance(){        return SingletonHolder.sInstance;    }    //静态内部类    private static class SingletonHolder{        private static final sInstance = new SingletonDemo3();    }}

通过代码可以发现,只有在第一次调用getSingleInstance()方法时,sInstance才会被初始化。

2.4、Double CheckLock式

大名鼎鼎的DCL实现方式:

public class SingletonDemo4{    //【☆☆☆】特别注意【volatile】关键字的修饰    private volatile static SingletonDemo4 mSingleInstance = null//私有化构造方法    private SingletonDemo4(){    }    //提供公有的静态方法,暴露接口供外部调用获取单例类    public static synchronized SingletonDemo4 getSingleInstance(){        if (mSingleInstance == null) {            synchronized(SingletonDemo4.class){                if (mSingleInstance == null) {                    mSingleInstance = new SingletonDemo4();                }               }        }        return mSingleInstance;    }}

可以看到,这里执行了两次判空操作。第一次是为了避免不必要的同步,第二次是为了使得在对象为null时创建。

关于DCL中【volatile】关键字的修饰,这里特别附上一篇文章供大家参考:java单例真的写对了么

2.5、枚举式(据称是最佳单例实现模式)

嗯,除了写起来非常简单,也没有什么更合适来形容枚举了。

public enum SingletonDemo5{    SINGLEINSTANCE;}

【☆☆☆】特别注意的是:java中默认枚举实例的创建是线程安全的,并且任何情况下都是单例。


3、序列化与反序列化

额外补充一点序列化与反序列化的知识。
先看大白话定义:

Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。

序列化我们用的比较多,因此这里重点来看反序列化。
反序列化就是将字节序列恢复成java对象。而构造一个java对象是需要调用其构造方法的,我们都知道单例模式中构造方法是私有化的,那么这里应该怎么考虑这个问题?

只需要在单例类中加入一个钩子方法:readResolve()即可

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

添加了上述方法,就能保证反序列化时返回我们的单例对象,而不是再重新创建违反了单例原则。

原创粉丝点击