【Linux】单例模式 — 饿汉与懒汉

什么是单例模式

单例模式是一种经典的,常用的。常考的软件设计模式,也是设计模式中最简单的形式之一。
它的核心结构中只包含一个被称为单例的特殊类,即单例模式可以保证系统中,应用该模式的一个类只有一个对象实例。因此这一模式的目的就是使得类的一个对象成为系统中的唯一实例。
设计模式就是编程大佬们针对一些经典的常见场景,总结的一些对应的解决方案。

单例模式的特点

  1. 一个类只能有一个实例
  2. 自己创建这个实例
  3. 整个系统都要使用这个实例

单例模式的优点

一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。

单例模式的缺点

一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
二、可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
三、对象生存期问题
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。

实现单例模式

实现单例模式有两种形式,饿汉方式和懒汉方式。

什么是饿汉?什么是懒汉?

概念不太好理解,我举个例子大家应该就能明白了。

这里是个洗碗的例子。
第一种:吃完饭立刻洗碗因为下顿饭好了的时候可以立即吃到饭,这是饿汉方式:时刻准备着
第二种:吃完饭把碗放下,等吃下顿饭的时候再去洗碗,这是懒汉方式:用的时候再准备

有没有发现二者的区别呢?

其实饿汉模式的核心思想是立即加载,即在使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说,很着急的样子);
懒汉模式的核心思想是“延时加载”,即在调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急),从而也能够优化服务器的启动速度。
懒汉模式与线程中的写实拷贝有异曲同工之妙,写实拷贝也提高了创建子进程的速度。

饿汉方式

public class Singleton {

    // 将自身实例化对象设置为一个属性,并用static、final修饰5同时提前准备好
    private static final Singleton instance = new Singleton();
    
    // 构造方法私有化
    private Singleton() {}
    
    // 静态方法返回该实例
    public static Singleton getInstance() {
        return instance;
    }
}
/* 第二种写法*/
template<typename T>  //使用模板类
class Singleton{
  private static T data;   //提前准备好数据data
  public:
  static T* getInstance()  //提供外部调用方法
  {
    return &data;
  }
};

通过上面Singleton这个被包装好的类,我们可以保证在一个进程中只能有一个T对象的实例。

“饿汉模式”的优缺点:

优点:实现起来简单,没有多线程同步问题。

缺点:当类SingletonTest被加载的时候,会初始化static的对象,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。

懒汉方式

template<typename T>
class Singleton{
//将自身实例化对象设置为一个属性,并用static修饰
  private static T* inst;
  public:
  //静态方法返回该实例
  static T* GetInstance()  
  {
    if(inst==NULL)
    {
      inst=new T(); //用的时候才new一个对象
    }
    return inst;
  }
};

“懒汉模式”的优缺点:

优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。

缺点:在多线程环境中,这种实现方法是错误的,存在一个严重的问题:线程不安全!!,根本不能保证单例的状态。因为该类在第一次调用的时候,如果被两个线程同时调用则可能会创建出两份T对象的实例,不符合单例模式的要求。不过后续再次调用是不会有问题的。

因此我做出如下的修改:

线程安全版:懒汉模式

template<typename T>
class Singleton{
 volatile static T* inst;  //防止被编译器优化
 static std::mutex lock;
  public:
  static T* GetInstance()
  {
    if(inst==NULL)   //双重判断降低锁冲突的概率,提高性能
    {
      lock.lock();
      if(inst==NULL){
      	inst=new T(); //用的时候才new一个对象
      }
      lock.unlock();
    }
    return inst;
  }
};

优化后的代码,在多线程情形下,保证了“懒汉模式”的线程安全。
缺点:众所周知在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。

代码书写的注意事项:

  1. 加锁解锁的位置,要在第一次判空后进行加锁,创建单例对象成功后才解锁
  2. 双重if 判断,可以减少锁的消耗,避免不必要的锁竞争
  3. volatile 关键字保持内存可见性,防止编译器过度优化

单例模式就总结到这里啦,下篇博客博主准备总结网络相关知识了,不要忘记关注博主哦~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值