单例模式--改进版

来源:互联网 发布:数据库设计包括 编辑:程序博客网 时间:2024/06/10 19:35
C++讲究的是代码的封装和复用,那么今天学习的设计模式,就是为了提高代码的可重用性。单例模式是最常见,最基本的设计模式。
 单例模式特点:保证系统中一个类只能有一个实例而且易于被外界访问。

 为什么会有单例模式?
    对于一个系统来说,只有一个实例很重要,例如,一个系统中可以存在多个打印机,但是只能有一个正在工作的任务;windows只能打开一个任务管理器,如果你不这些窗口进行控制的话,将会弹出多个窗口,如果这些窗口的内容完全一致的话,就是重复对象,浪费资源。如果弹出来的内容不一致,那么系统在这个瞬间是存在多个状态,与实际不相符,也会让人很难理解。由此可见,实例化对象的唯一划,就很重要了。

单例模式的三个必须:一个类必须只有一个实例;自己必须自行创造这个实例;必须自行向整个系统提供这个实例。

模式分析:
单例模式保证一个类只有一个实例,并提供一个访问它的全局访问点。单利模式拥有一个私有的构造函数,确保用户无法通过new来直接实例化它,你将它的构造函数私有了,那么无法使用它,那么不就是聋子耳朵--样子了。所以必须提供一个public的静态方法,以确保其他的对象可以同过这个静态方法获取到它。

单例模式模式的代码实现:
第一次代码:
class singleton
{
    public:
        static singleton *getIntance()
        {
            return &object;
        }
    private:
        singleton(){}//构造函数
        static singleton object;
}
singleton singleton::object :
一个私有的构造函数,通过public方法*getlintance()这个方法得到这个实例。在私有构造函数之后多了一个静态的对象,这样子每一次就只能获取到同一个实例,而且你操作也是同一个实例。

也许聪明的你,已经发现了问题所在,那就是你看到一个全局变量,大家都知道全局变量在函数刚开始的时候,就会先加载到内存中,那么无论你是否在以后使用,都会先装载。这样子在效率上面,是不是就很落后,然整个程序出现了一个慢启动。所以我们第二次来一个修改。

第二次修改后的代码:
class singleton
{
    public:
        static singleton *getIntance()
        {
            if(pobject == NULL)
            {
                pobject = new singleton();
            }
            return pobject;
        }
    private:
        singleton(){}//构造函数
        static singleton *pobject;
}
singleton *singleton::pobject = NULL :

改进后代码的分析:我们将一个静态变量换成一个静态的指针,这样子每次加载的时候,只需要加载4个字节的(32位)指针大小就可以了。每次通过getlntance获取实例的时候,都会先进判断,看是不是已经有一个对象了。

其实你要是能想到这一步,说明你还是很注重代码效率的人,那么觉得这样子就完美了吗?

想一想安全,你有考虑吗?什么是安全,这个代码安全吗?

线程安全:就是在多个线程调用时,会不会出现竟态现象。什么事竟态条件?简单的说,就是随着多个线程的调用顺序的不同,看他的结果是否相同!如果不同,那就是不安全的,那么问题来了,这个第二次修改的过程中,我们只看到了判断是不是已经实例,假如:第一个线程比较快。他自己到了,发现这个实例还没有实例化,那么它可开心了,就开始去new,可是谁知道new的时候太慢了,这时候也来了一个线程,这个一看也没有已经实例的对象,于是自己也去new。简单例子说明一个简单安全问题,这个不是一个原子操作,存在安全风险。

也许你会想到加上互斥锁,这说明你还是有不错的LINUX安全的基础的,问题出现了进行解决,第三次修改;

第三次修改后的代码:
class singleton
{
    public:
        static singleton *getIntance()
        {
            pthread_mutex_lock(&mutex);
            if(pobject == NULL)
            {
                pobject = new singleton();
            }
            pthread_mutex_unlock(&mutex);
            return pobject;
        }
    private:
        singleton(){}//构造函数
        static singleton *pobject;
}
singleton *singleton::pobject = NULL :

简单的互斥锁,解决了上个例子中两个线程的竟态条件,第一个线程先到就先开始new,而线程到的时候,发现有个人正在关门创造对象,那没办法你也没有对象,也不能new,所以就只能等待了。竟态问题解决了。会不会就会出现其他的问题。不出现问题是不可能的。

这么完美还有什么问题呢?问题只要你发现它总会有的;


它的装载你考虑了,他的安全你也考虑了,但是你有没有想过一些极端的,加了这个加了互斥锁,安全了确实安全了,可是你想过不是所有的程序都会有那么多线程的,如果我是一个单线程,这样子我每次得到一个实例的时候,都会先进性一个加锁,解锁。浪费时间,针对于多线程下,存在的单线程也得有效率呀,

第四次修改代码:
class singleton
{
    public:
        static singleton *getIntance()
        {

            if(pobject == NULL)
            {
                pthread_mutex_lock(&mutex);
                if(pobject == NULL)
                {
                     pobject = new singleton();
                }
                pthread_mutex_unlock(&mutex);
            }

            return pobject;
        }
    private:
        singleton(){}//构造函数
        static singleton *pobject;
}
singleton *singleton::pobject = NULL :

双重判断,如果你是单线程的话,第一次没有实例,就先开始自己关起门来new,等到下次再得到这个实例的时候,就先外层的判断,一看已经有一个实例了,就不用在进入,不用每次加锁,解锁。在多线程下,单线程也是高效的。

对于单例模式的改进,我只能到这里了,欢迎你提出更好的改进方案。
原创粉丝点击