设计模式之单例模式

来源:互联网 发布:商务英语写作 知乎 编辑:程序博客网 时间:2024/04/29 08:00

关于设计模式的学习笔记,教材:《设计模式的艺术之道》 刘伟 著

class TaskManager{    private static TaskManager tm=null;    private TaskManager(){    //大量初始化工作     }    public static TaskManager getInstance(){        if(tm==null){            tm=new TaskManager();        }        return tm;    }}

一般的单例模式:

  1. 将构造函数私有
  2. 定义静态的TaskManager类型私有成员变量tm
  3. 增加一个公有的静态方法,返回tm

但这样会出现问题。
比如当多次调用getInstance()方法,而new TaskManager()时因为大量初始化工作需要消耗不少时间时,由于tm尚未创建成功,于是其他调用的getInstance()方法中,tm仍为null,可能会导致创建了多个tm对象。违背了单例模式的初衷。

一开始人们想到两种解决方法:
1.Eager Singleton
在类加载的时候就已经创建单例对象

class TaskManager{    private static final TaskManager tm=new TaskManager();    //其余同上}

2.Lazy Singleton
在类加载时并不实例化,而是在需要时再加载,这种技术又称为Lazy Load(延迟加载技术)。为了避免多个线程同时调用getInstance()方法,在Java中使用关键字synchronized

 class TaskManager{    synchronized public static TaskManger getInstance(){        if(tm==null){            tm=new TaskManager();        }        return tm;    }}

通过增加关键字synchronized进行线程锁定,以处理多个线程同时访问的问题。但是在多线程高并发访问环境下,会导致系统性能大大降低。比如当tm已经创建,而同时调用多次getInstance(),实际上不会并发执行,实际上只需直接return,但要排队。
于是有了这个改进:

 class TaskManager{    public static TaskManger getInstance(){        if(tm==null){            synchronized (TaskManager.class){                tm=new TaskManager();            }        }        return tm;    }}

似乎问题解决了,但是如果在某一瞬间,线程A和B都在调用getInstance(),此时均能通过tm==null的判断,那么依然new了两次。因此需要进一步改进。在synchronized 锁定代码中再进行一次tm==null的判断,这称为Double-Check Locking。

 class TaskManager{    private volatile static final TaskManager tm=null;    public static TaskManger getInstance(){        if(tm==null){            synchronized (TaskManager.class){                if(tm==null){                    tm=new TaskManager();                }            }        }        return tm;    }}

一开始我有个疑惑,外面的if(tm==null)是否可以忽略?但仔细一想,如果忽略,那就跟最开始把整个方法都synchronized 差不多了。
这种Double-Check Locking需要在静态成员变量tm添加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能正确处理。(jdk1.5以上)但是由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用Double-Check Locking依然不好。

比较两种方法
Eager无须考虑多线程访问时的问题,而且因为一开始就实例化好,所以调用速度和反应时间由于Lazy。但是无论系统在运行时是否需要使用该单例对象,在类加载时都创建好了,就会消耗更多系统资源,而且加载时间可能会比较长。

关于类的加载:http://blog.csdn.net/ns_code/article/details/17881581

而Lazy无须一直占用系统资源,但是必须处理好多线程同时访问问题,多线程同时首次引用此类时需要双重检查锁定等机制进行控制,将导致系统性能受到影响。

一种更好的方法

 class TaskManager{    private TaskManager(){}    private static class HolderClass{        private static final TaskManager tm=new TaskManager();    }    public static TaskManger getInstance(){        return HolderClass.tm;    }}

类加载时没有实例化TaskManager,而第一次调用getInstance()时加载内部类,初始化其成员变量tm,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。
这种方法叫Initialization on Deman Holder技术。
通过IoDH,既可以实现Lazy Load,减少系统资源消耗,又保证线程安全,不影响系统性能。缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH。

单例模式的优缺点
优点

  1. 提供了对唯一实例的受控访问
  2. 节约系统资源
  3. 允许可变数目的实例

缺点

  1. 没有抽象层,类的拓展有很大困难
  2. 职责过重,即提供业务方法,又提供了创建对象的方法,即既有对象的创建,也有对象的使用
  3. 很多语言如Java、C#的运行环境都提供了自动垃圾回收技术,因此,如果实例化的单例对象长时间不被使用,系统会认为是垃圾,自动销毁并回收。下次使用时重新实例化,这将导致共享的实力对象状态丢失。

适用场景

  1. 系统只需要一个实例对象
  2. 客户调用类的单个实例只允许使用一个公共访问点。
0 0