设计模式学习笔记—单例模式

来源:互联网 发布:积分购物源码 编辑:程序博客网 时间:2024/05/22 03:04

对于一个单例来说,应该注意的大概只有下面一点。

确保程序从始至终只能操纵一个对象。

首先需要考虑的是一个单例必须要注意的问题。

1.保证这个类只能被创建一次。2.整个程序只能访问到一个对象。

基于这几点可以设计出这样的一个类。

Singleton.java

public class Singleton {    // 静态变量保证应用程序只有一个变量    private static Singleton mInstance;    // 私有化构造方法保证对象只能在这个类内部被创建    private Singleton() {        ...    }    // 为其它类提供一个可以获得这个变量的公共方法    public static Singleton getInstance() {        return mInstance;    }}

接下来需要考虑的就是何时去创建这样一个对象,一般来说常用的有两种方法“懒汉式(Lazy initialization)”和“饿汉式(Eager initialization)”。
“懒汉式”就是在这个对象第一次被使用的时候进行创建,而“饿汉式”就是在类进行初始化的时候就创建”,这两种方法的加载分别会导致两种不同的问题。

“懒汉式”改动的代码

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

导致的问题就是,当两个线程同时去获得这个单例时,有可能会获得两个不同的对象,这种做法是线程不安全的,不考虑执行效率问题的情况下,可以添加 synchronized 来保证这个方法同时只有一个线程在访问,更改后的代码如下

    public static synchronized Singleton getInstance() {        ...    }

“饿汉式”改动的代码

    private static final Singleton mInstance = new Singleton();

看起来这种方法简单易懂,也不用考虑线程安全问题,但如果程序中任何以这种方式创建的静态变量过多,或者单例构造方法中有比较耗时的操作,则这个程序的启动时间就会变长,而且这个对象无论有没有被使用都会被创建,算是一种资源上的浪费。然而最大的问题是有些情况下无法使用“饿汉式”,比如单例的构造方法中有参数。

所以在保证线程安全的情况下,“懒汉式”比“饿汉式”节省程序启动时间与对象的所占用的内存。“饿汉式”比“懒汉式”节省第一次使用单例时所消耗的时间。
然而,这两种方式并没有什么大的区别,个人感觉“懒汉式”适用范围更广一些,因为有好多事情可能需要在程序启动的时候要做,单例对象的话,能省点时间是一点,况且用户在使用时感觉偶尔的一次卡顿,也许是系统卡了一下也说不定。(๑•́ ₃ •̀๑)

最后,说明一下上面那种“懒汉式”的执行效率问题,线程同步是很费时间的,所以,单例类最好写成这样

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

这个处理方式的话叫做“双重检查锁(double-checked locking)”,确保了线程安全的前提下又提升了执行效率,然而会发现一个并不认识的关键字 volatile ,这个关键字的作用是防止下面这种情况发生。

线程1 首次访问 getInstance 方法,mInstance 为空, 将 mInstance 对象指向分配的内存空间,同时,线程2 访问,mInstance 不为空,线程2 获得 mInstance 对象的复制, 由于线程锁的问题所以线程2 需要等待线程1 构造方法执行完毕,线程2调用 mInstance 的方法,但由于是复制的时候线程1 还没有把真正的数据写入主内存中,所以线程2 获得的对象实际内存内容为空,然后报错。o(^▽^)o~♪
(以上内容由于是我自己编的,而且很难进行测试,所以看看就好。)

volatile 的作用,就是跳过线程对对象的复制,直接对主内存进行操作(大概),保证线程安全,反正单例的话这样写没错就对了(1.5之前的java应该没人用了吧)。

参考资料

Singleton pattern
HeadFirst设计模式
单例模式、双检测锁定DCL、volatile

0 0
原创粉丝点击