单例模式——懒汉式饿汉式

Posted pdjdghrs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式——懒汉式饿汉式相关的知识,希望对你有一定的参考价值。

一、懒汉式——DCL写法

  优点:解决线程、效率高

package com.atguigu.single02;
/*
    单例模式类:保证对象唯一性
    懒汉式:线程安全问题 DCL写法(双重检查)(面试终极写法)

* */
public class Single {
    //本类的成员位置,创建自己的对象
    private static Single single = null;
    private Single(){}

    /*
        为了提供程序执行效率,同步代码块(不要同步方法)
        线程操作的共享数据
    * */
    public static Single getInstance(){
        if ( single == null){
            synchronized(Single.class){
                if (single == null)
                    single = new Single();
            }
        }

        return single;
    }
}

  测试代码:

@Test
    public void testSingle02(){
        /*Single single1 = Single.getInstance();
        System.out.println("single = " + single1);
        Single single2 = Single.getInstance();
        System.out.println("single = " + single2);*/
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                Single single1 = Single.getInstance();
                System.out.println("single = " + single1);
            }).start();
        }

    }

二、饿汉式

  优点:线程安全

  缺点:效率低

package com.atguigu.single01;

/*
    单例模式类:保证对象唯一性
    饿汉式
    1. 私有修饰构造方法
    2. 本类的成员位置,创建自己的对象
        自己new自己
    3. 提供公共的方法,返回对象
* */
public class Single {
    private  static Single single = new Single();
    private Single(){}
    /*
        类文件 Single.class被加载器加入到内存中
        内存的那个位置:栈,堆,方法区,寄存器,本地方法栈
        Java的好多功能,JDK没有,调用操作系统实现:本地方法,进入本地方法栈运行

        class:方法区(JDK8-元数据区):初始化自己的静态成员

    * */

    public static Single getInstance(){
        return single;
    }

}

  测试代码:

@Test
    public void testSingle01(){
        Single single1 = Single.getInstance();
        System.out.println("single = " + single1);
        Single single2 = Single.getInstance();
        System.out.println("single = " + single2);

    }

 

C++单例模式的实现(懒汉式饿汉式)

单例模式-Singleton

名词解释

数学与逻辑学中,singleton定义为:有且仅有一个元素的集合
单例模式最初的定义出现在《设计模式》(艾迪生维斯理,1994):"保证一个类仅有一个实例,并提供一个访问它的全局访问点。"

动机

对于系统中的某些类来说,只有一个实例很重要:

例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。

如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。
因此有时确保系统中某个对象的唯一性一个类只能有一个实例非常重要。

要点

根据定义,显然单例模式的要点有三个:

  1. 某个类只能有一个实例;
  2. 它必须自行创建这个实例;
  3. 它必须自行向整个系统提供这个实例。

从具体实现角度来说:

  1. 单例模式的类只提供私有的构造函数
  2. 类定义中含有一个该类的静态私有对象
  3. 该类提供了一个静态的公有函数用于创建或者获取它本身的静态私有对象。

饿汉式

根据名字就可以知道:饿汉
因为饿,所以猴急,巴不得第一时间就把食物送到嘴边;
因此,这个模式的单例模式唯一的实例是在主函数运行之前就产生了的!

实现方式

根据上述要点,不难实现如下代码:

class Singleton

public:
    static Singleton *getinstance()
    
        return &instance;
    
private:
    static Singleton instance;
    Singleton() 
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
;
Singleton Singleton::instance;

运行结果

通过如下测试代码,我们来看饿汉单例模式的结果:

int main()

    Singleton *a1 = Singleton::getinstance();
    Singleton *a2 = Singleton::getinstance();
    Singleton *a3 = Singleton::getinstance();

    cout << a1 << endl;
    cout << a2 << endl;
    cout << a3 << endl;

    return 0;

根据定义我们知道,a1a2a3打印出来应该是同一个地址

可以看到,是同一个地址

懒汉式

有了上面饿汉式的经验,我们同样可以这么想:
懒汉式,因为,所以唯一实例能拖就拖
一直等到有人要使用的时候,才不得不构造

常规实现

同样的,根据定义我们可以写出如下代码:

class Singleton

public:
    static Singleton *getinstance()
    
        if (instance == nullptr)
            instance = new Singleton();
        return instance;
    

private:
    static Singleton *instance;
    Singleton() 
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
;
Singleton *Singleton::instance = nullptr;

运行结果:

虽然运行结果没有问题,
但是,这个代码是有问题的:
线程不安全

对于if (instance == nullptr)instance = new Singleton()这两句来说,如果处在多线程环境下:

我们会发现这种情况下,会执行多次对象的构造,这和我们的初衷不符!

线程安全版实现

为了避免上述问题,我们改进代码:
加入线程互斥锁(mutex)

std::mutex mtx;
class Singleton

public:
    static Singleton* getinstance()
    
        if(instance == nullptr)
        
            lock_guard<std::mutex> lck(mtx);
            if(instance == nullptr)
            
                instance = new Singleton();
            
        
        return instance;
    
private:
    static Singleton* instance;
    Singleton()
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
;
Singleton* Singleton::instance = nullptr;

注意:
为了避免线程安全问题:需要进行锁+两次判断
避免因为一个线程后续操作未完成,而使得后来的线程进入if语句

可以看到运行正确

精简实现

对于懒汉式来说,还有更精简的一种写法:

class Singleton

public:
    static Singleton* getinstance()
    
        static Singleton instance;
        return &instance;
    
private:
    Singleton()
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
;

运行结果如下:

注意:
这个精简版之所以也是线程安全的,是因为,在底层对于static静态局部变量的初始化,编译器会自动加锁和解锁
将上述代码在Linux通过g++编译,命令如下:
g++ -o 单例模式 单例模式.cpp -g

接下来启动gdb调试:
gdb 单例模式 -q

接下来输入l:(注意是英文L的小写

接着下断点:b 10

接着运行到断点处:run

接下来执行:disassemble getinstance

我们可以看到,底层的汇编是自动加锁解锁的!

参考资料

【1】单例模式(百度百科)

以上是关于单例模式——懒汉式饿汉式的主要内容,如果未能解决你的问题,请参考以下文章

C++单例模式的实现(懒汉式饿汉式)

C++单例模式的实现(懒汉式饿汉式)

单例模式实现方法之懒汉式饿汉式

单例模式

单例设计模式

单例模式详解