【设计模式】单例模式 1、单例模式的定义 2、单例模式的实现 3、优点与缺点 4、使用场景

  单例模式( Singleton Pattern) 是一个比较简单的模式, 其定义如下:
  Ensure a class has only one instance, and provide a global point of access to it.( 确保某一个类只有一个实例, 而且自行实例化并向整个系统提供这个实例。 )
  单例类, 通过构造私有化确保了在一个应用中只产生一个实例, 并且是自行实例化的( 在Singleton中自己使用new Singleton()) 。

2、单例模式的实现

2.1、经典模式

class singleton
{
protected:
    singleton(){}
private:
    static singleton* p;
public:
    static singleton* instance();
};

singleton* singleton::p = NULL;
singleton* singleton::instance()
{
    if (p == NULL)
        p = new singleton();
    return p;
}

  这种模式不是线程安全的,如果线程a和线程b同时调用instance,那么两个线程都会创建一个示例,这明显是不对的。

2.2、懒汉模式

  不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上边的经典方法被归为懒汉实现;

  经过上面的演示,明显懒汉模式是线程不安全的。那么怎么保证线程安全呢?加锁!

  加锁的经典模式

class singleton
{
protected:
    singleton()
    {
        pthread_mutex_init(&mutex);
    }
private:
    static singleton* p;
public:
    static pthread_mutex_t mutex;
    static singleton* initance();
};

pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;
singleton* singleton::initance()
{
    if (p == NULL)
    {
        pthread_mutex_lock(&mutex);
        if (p == NULL)
            p = new singleton();
        pthread_mutex_unlock(&mutex);
    }
    return p;
}

  内部静态变量的懒汉实现

  此方法也很容易实现,在instance函数里定义一个静态的实例,也可以保证拥有唯一实例,在返回时只需要返回其指针就可以了。

class singleton
{
protected:
    singleton()
    {
        pthread_mutex_init(&mutex);
    }
public:
    static pthread_mutex_t mutex;
    static singleton* initance();
};

pthread_mutex_t singleton::mutex;
singleton* singleton::initance()
{
    pthread_mutex_lock(&mutex);
    static singleton obj;
    pthread_mutex_unlock(&mutex);
    return &obj;
}

2.3、饿汉模式

  饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

class singleton
{
protected:
    singleton()
    {}
private:
    static singleton* p;
public:
    static singleton* initance();
};

singleton* singleton::p = new singleton;
singleton* singleton::initance()
{
    return p;
}

  为什么说饿汉模式是线程安全的呢?因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心多线程问题。

 2.4、特点与选择

  由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。

  在访问量较小时,采用懒汉实现。这是以时间换空间。

3、优点与缺点

3.1、优点

  由于单例模式在内存中只有一个实例, 减少了内存开支, 特别是一个对象需要频繁地创建、 销毁时, 而且创建或销毁时性能又无法优化, 单例模式的优势就非常明显。
由于单例模式只生成一个实例, 所以减少了系统的性能开销, 当一个对象的产生需要比较多的资源时, 如读取配置、 产生其他依赖对象时, 则可以通过在应用启动时直接产生一个单例对象, 然后用永久驻留内存的方式来解决。
  单例模式可以避免对资源的多重占用, 例如一个写文件动作, 由于只有一个实例存在内存中, 避免对同一个资源文件的同时写操作。
  单例模式可以在系统设置全局的访问点, 优化和共享资源访问, 例如可以设计一个单例类, 负责所有数据表的映射处理。

3.2、缺点

  单例模式一般没有接口, 扩展很困难, 若要扩展, 除了修改代码基本上没有第二种途径可以实现。 单例模式为什么不能增加接口呢? 因为接口对单例模式是没有任何意义的, 它要求“自行实例化”, 并且提供单一实例、 接口或抽象类是不可能被实例化的。 当然, 在特殊情况下, 单例模式可以实现接口、 被继承等, 需要在系统开发中根据环境判断。
  单例模式对测试是不利的。 在并行开发环境中, 如果单例模式没有完成, 是不能进行测试的, 没有接口也不能使用mock的方式虚拟一个对象。
  单例模式与单一职责原则有冲突。 一个类应该只实现一个逻辑, 而不关心它是否是单例的, 是不是要单例取决于环境, 单例模式把“要单例”和业务逻辑融合在一个类中。

4、使用场景

  在一个系统中, 要求一个类有且仅有一个对象, 如果出现多个对象就会出现“不良反应”, 可以采用单例模式, 具体的场景如下:
  要求生成唯一序列号的环境;
  在整个项目中需要一个共享访问点或共享数据, 例如一个Web页面上的计数器, 可以不用把每次刷新都记录到数据库中, 使用单例模式保持计数器的值, 并确保是线程安全的;
  创建一个对象需要消耗的资源过多, 如要访问IO和数据库等资源;
  需要定义大量的静态常量和静态方法( 如工具类) 的环境, 可以采用单例模式( 当然, 也可以直接声明为static的方式) 。