设计模式之-单例模式

来源:互联网 发布:辐射4帅男捏脸数据教程 编辑:程序博客网 时间:2024/06/05 16:23

1,为什么要有单例模式

在实际的开发中,有时候为了节约系统资源,有时候需要确保系统中某个类只有一个实例,我们需要这个类的实例第一次创建之后就无法再创建其它的实例,为了确保这个类实例的唯一性,我们需要使用单例模式来创建这个类。

单例模式(SingIeton Pattern)概念:

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个单例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

单例模式的三要素:a,自行创建;b,类只能有一个实例;c,它必须向整个系统提供这个单例。

单例模式 结构图:


2,单例模式概述

举个例子,windows上的资源管理器,在操作系统运行的时候,系统需要只保留一份资源管理器的对象,因为资源管理器的启动和运行需要占用较多的资源和时间,另外,多次去获取系统cpu等得状态也没有意义,这时候可以使用单例模式来设计这个资源管理器的类。

下面用C++模拟资源管理器:

class Danli{public:    int num;public:    Danli(); //初始化方法public:    void displayProcesses(); //显示进程    void  displayServices(); //显示服务    void displayCPUUse(); //显示粗皮占用率};
为了实现内存中这个类只有一个对象,我们需要:

a,由于每次使用new关键字,调用构造方法就能产生一个类的对象,因此我们可以将构造函数设为private类型,,这样在类的外部就无法调用构造函数,也就无法创建类的实例。

private:    Danli(); //初始化方法
b,将构造函数的声明为private类型的,虽然无法在类的外部无法创建类的对象,但是在类的内部还是可以创建的,为了保存我们创建的这个类的实例,我们可以在类中创建一个静态的私有的本类类型的变量,用这个变量来保存这个唯一的实例。

private:     static Danli *danli;
c,为了保证程序的封装性,我们可以讲保存对象的变量设置为私有的,怎么在外面访问呢?我们可以定义一个类方法,通过类方法返回这个私有的静态的成员变量。
public:<pre name="code" class="objc"><pre name="code" class="cpp">    static Danli *GetInstance()    {        if (danli == nullptr) {            danli = new Danli;        }        return danli;    }


这个方法,执行的时候,如果danli指针为空,那么创建对象并赋值,如果不为空直接返回这个对象,使用这种方式可以保证本类的对象是唯一的。       

注意:返回单例对象的这个方法,首先必须是public类型的,这样才能在类的外部访问,其次必须是类方法,在C++中就是static方法,这样就不需要创建这个类的对象就可以直接调用这个方法,并得到这个类的对象。这就是简单的单例模式。

通过以上几步的实现我们完成了简单的单例模式的代码:

class Danli{public:    int num;private:    static Danli *danli;    Danli(); //初始化方法public:    void displayProcesses(); //显示进程    void  displayServices(); //显示服务    void displayCPUUse(); //显示粗皮占用率        static Danli *GetInstance()    {        if (danli == nullptr) {            danli = new Danli;        }        return danli;    }};
单例模式结构图中只包含一个单例角色:
      ● Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

3,单例模式实现的几种方式

如上面所示的例子中,看似单例模式已经完美实现,但是,如果遇到并发线程非常多的时候,可能还会出错,第一个线程判断danli为空,开始创建进入if入局创建对象,可能Danli这个对象非常复杂,创建初始化需要时间,就在它初始化的同时。另一个线程也判断dnali是否为空,由于第一个线程的创建尚未完成,因此这时候danli对象还是空的,那么第二个个对象也会去创建一个对象,这样就造成创建了两个或者多个单例对象。解决方案如下。

1)饿汉式单例

饿汉式单例是比较简单的一种单例模式,结构图如下:


饿汉式单例,就是在初始化类中静态变量的同时初始化静态变量,因此在调用单例类得到单例对象时候,事实上单例类已经创建完成,可以保证单例对象的唯一性,代码如下:

private:    static Danli *danli = new Danli();

2)懒汉式单例和线程锁

懒汉式单例 是在第一次调用getInstance()的时候创建单例对象,在类加载的时候并不会创建单例对象,这种技术称为延迟加载(Lazy Load)技术,为了避免多线程时候同时创建,这时候可以使用线程锁,同一时刻只允许一个线程访问。
懒汉式单例结构图:

示例代码,java代码:

<pre name="code" class="java">class LazySingleton {       private static LazySingleton instance = null;         private LazySingleton() { }         synchronized public static LazySingleton getInstance() {           if (instance == null) {              instance = new LazySingleton();           }          return instance;       }  }

在java中我们使用 synchronized 关键字给一段代码加上线程锁,这样就可以防止多个线程同时访问一段代码,如上的代码可以成功的解决问题。但是,上述方法虽然解决了线程安全问题,但程序在每次获取单例对象的时候,都需要判断线程锁定状态,如果是在多线程并发性很高的情况下,就会导致系统运行缓慢,效率低下。事实上,我们只需要给    instace = new LazySingLeton()    这句话加上线程锁就够了,修改后的代码如下:

class LazySingleton {       private static LazySingleton instance = null;         private LazySingleton() { }         synchronized public static LazySingleton getInstance() {           if (instance == null) {              <span class="keyword" style="line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; margin: 0px; padding: 0px; border: none; color: rgb(0, 102, 153); font-weight: bold; background-color: inherit;">synchronized</span><span style="line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; margin: 0px; padding: 0px; border: none; background-color: inherit;"> (LazySingleton.</span><span class="keyword" style="line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; margin: 0px; padding: 0px; border: none; color: rgb(0, 102, 153); font-weight: bold; background-color: inherit;">class</span><span style="line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; margin: 0px; padding: 0px; border: none; background-color: inherit;">) {  </span><span style="line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; background-color: inherit;">            instance = </span><span class="keyword" style="line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; margin: 0px; padding: 0px; border: none; color: rgb(0, 102, 153); font-weight: bold; background-color: inherit;">new</span><span style="line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; margin: 0px; padding: 0px; border: none; background-color: inherit;"> LazySingleton();   <span style="background-color: inherit;">        } </span></span>        }          return instance;       }  }
这样看问题貌似是解决了,但是在并发性高的多线程程序中,还是会同时一个或者两个线程进入到if条件里面,虽然new被锁定,但是进入if内部的线程会等待解锁,解锁之后还是会去重新创建对象,这样还是避免不了创建多个对象的情况。违背了单例模式的设计思想。   还需要改进,这时候可以在已锁定的代码中加第二层判断,然后锁定,这种方式叫做:双重检查锁定(Double-Check Locking)。


代码如下:

class LazySingleton {       private volatile static LazySingleton instance = null;         private LazySingleton() { }         public static LazySingleton getInstance() {           //第一重判断          if (instance == null) {              //锁定代码块              synchronized (LazySingleton.class) {                  //第二重判断                  if (instance == null) {                      instance = new LazySingleton(); //创建单例实例                  }              }          }          return instance;       }  } 
需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。

3)饿汉式单例类 和 懒汉式单例类 的比较

饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。
      懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理好多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。

4)一种更好的方法实现单例类

懒汉式单例类和饿汉式单例类最终都可以实现线程安全,但是各有优缺,饿汉式单例类,不管系统是否会用到这个类对象,类对象都会被创建,当这个类功能特别复杂时候,就会导致这个类的创建耗费大量的时间和系统资源;懒汉式单例类,使用线程锁保护线程安全,但是在初始化的时候会导致效率低下。   还有一种更好的实现单例类的方式    Initialization Demand Holder (IoDH)的技术

在IoDH中,需要在类内部加一个静态内部类,在静态内部类里面创建单例对象,然后通过getInstance方法返回给外部使用。

代码如下:

//Initialization on Demand Holder  class Singleton {      private Singleton() {      }            private static class HolderClass {              private final static Singleton instance = new Singleton();      }            public static Singleton getInstance() {          return HolderClass.instance;      }            public static void main(String args[]) {          Singleton s1, s2;               s1 = Singleton.getInstance();          s2 = Singleton.getInstance();          System.out.println(s1==s2);      }  }  

由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。

通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH)。

实现单例模式的三种方式:饿汉式单例,懒汉式单例,IoCH。

4,单例模式总结

1)单例模式的优点

a,单例模式提供了一种唯一实例的受控访问。因为单例类封装了它的唯一实例,因此合一控制用户该怎样访问和何时访问。

b,由于在系统的内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。

c,允许可变数目的单例,基于单例模式我们可以进行扩展,使用和单例模式相似的方法来创建制定数量的对象,这样既能节约系统资源,又能解决单例中单例对象共享过多有损性能的问题。

2)单例模式的缺点

a,由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

b, 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

c,现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。

3)单例模式使用场景

a,系统只需要一个对象,比如资源管理器,或者对象需要消耗大量资源而系统只能保留一个对象的时候。

b,客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。


5,OC中单例的实现

//<span style="font-family: Arial, Helvetica, sans-serif;">Hero.h</span>#import <Foundation/Foundation.h>@interface Hero : NSObject<NSCopying>+(id)shareHero;@end

//<span style="font-family: Arial, Helvetica, sans-serif;">Hero.m</span>#import "Hero.h"@implementation Herostatic Hero *singletonHero = nil;//+(id)shareHero//{//    static dispatch_once_t pOnce;//    dispatch_once(&pOnce, ^{//        singletonHero = [[[self class] alloc] init];//    });//    return singletonHero;//}+(id)shareHero{    //双重锁机制    if (singletonHero == nil)    {        @synchronized(self)        {            if (singletonHero == nil)            {                singletonHero = [[[self class] alloc] init];            }        }    }    return singletonHero;}+(id)new{    return [self alloc];}//避免调用alloc的时候生成新的实例+(id)allocWithZone:(NSZone *)zone{    if (singletonHero == nil)    {        @synchronized(self)        {            if (singletonHero == nil)            {                singletonHero = [super allocWithZone:zone];                return singletonHero;            }        }    }        return nil;}//避免调用copy时生成新的实例- (id)copyWithZone:(NSZone *)zone{    return singletonHero;}- (id)mutableCopyWithZone:(NSZone *)zone{    return [self copyWithZone:zone];}@end






0 0
原创粉丝点击