设计模式之单例模式

15 篇文章 0 订阅
6 篇文章 0 订阅

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

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值