设计模式之单例模式的预加载和懒加载实现——懒汉模式和饿汉模式以及如何确保线程安全

单例模式

通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)确保一个类中最多只有一个实例,并提供一个全局访问点。即一个类只实例化了一个对象,全局都在使用这一个对象。
单例模式的实现由分成了两种情况,饿汉模式和懒汉模式,又叫预加载和懒加载。

预加载(饿汉模式)

顾名思义,就是预先加载。也就是还没有使用该单例对象,但是,该单例对象就已经被加载到内存了,提前准备好,需要使用的时候直接使用就可以。

public class PreloadSingleton {
    // 实例属性
    public static PreloadSingleton instance = new PreloadSingleton();

    // 构造方法私有,即无法调用该构造方法进行类的实例化
    private PreloadSingleton(){

    }

    // 获取实例
    public static PreloadSingleton getInstance(){
        return instance;
    }
}

从该类的示例中我们可以看到,static修饰的instance对象在类的加载阶段就已经创建好了,我们在使用的时候可以通过PreloadSingleton.instance直接调用使用,也可以通过getInstance()方法获取到该对象。全局的使用只有这一个对象。
该模式即便是该对象未使用,也会创建该对象,将其加载到内存中,虽然在使用的时候能够迅速拿来使用,但是也牺牲了一部分内存空间。

懒加载(懒汉模式)

懒加载其实也叫做延迟加载、按需加载,是指当只有在使用时才进行加载,并不是提前准备好的。

public class Singleton {
    // 实例属性
    public static Singleton instance = null;

    // 构造方法私有,即无法调用该构造方法进行类的实例化
    private Singleton(){

    }

    // 获取实例
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

从该类的示例中我们可以看到,static修饰的instance对象在类的加载阶段一直没有进行实例化,我们在使用的时候需要通过getInstance()方法进行调用,该方法内部首先判断instance对象是否已经实例化过,如果尚未实例化则实例化以后再返回,如果已经实例化,则返回该对象即可。保证全局只有这一个实例化的对象。
该方式保证了内存空间的使用,即只有在使用该实例对象的时候才会进行创建,不会造成内存的浪费,但是一定程度上也降低了拿取该类的速度。

线程安全

  • 预加载只有一条语句return instance,这显然可以保证线程安全,因为其满足了原子性。但是,我们知道预加载会造成内存的浪费。
  • 懒加载不浪费内存,但是无法保证线程的安全。首先,if判断以及其内存执行代码是非原子性的。其次,new Singleton()无法保证执行的顺序性。

不满足原子性或者顺序性,线程肯定是不安全的,这是基本的常识,不再赘述。我主要讲一下为什么new Singleton()无法保证顺序性。我们知道创建一个对象分三步:

  1. 开辟内存
    memory = allocate();
  2. 实例化对象
    ctorInstance(memory);
  3. 设置对象引用指向该内存。
    instance=memory()

jvm为了提高程序执行性能,会对没有依赖关系的代码进行重排序,上面2和3行代码可能被重新排序。我们用两个线程来说明线程是不安全的。线程A和线程B都创建对象。其中,A2和A3的重排序,将导致线程B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象(线程不安全)。

时间线程A线程B
t1A1:分配对象的内存空间
t2A3:设置instance指向内存空间
t3B1:判断instance是否为null
t4B2:由于instance不为null,所以访问instance引用所指向的对象
t5A2:初始化对象
t6A4:访问instance引用所指向的对象

保证懒加载的线程安全

我们首先想到的就是使用synchronized关键字。synchronized加载getInstace()函数上确实保证了线程的安全。但是,如果要经常的调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间,如果对象已经实例化了,我们没有必要再使用synchronized加锁,直接返回对象。

public class Singleton {
       private static Singleton instance = null;
       private Singleton() {
       };
       public static synchronized Singleton getInstance() {
              if (instance == null) {
                     instance = new Singleton();
              }
              return instance;
       }
}

我们把sychronized加在if(instance==null)判断语句里面,保证instance未实例化的时候才加锁

public class Singleton {
       private static Singleton instance = null;
       private Singleton() {
       };
       public static synchronized Singleton getInstance() {
              if (instance == null) {
                     synchronized (Singleton.class) {
                           if (instance == null) {
                                  instance = new Singleton();
                           }
                     }
              }
              return instance;
       }
}

我们经过之前线程安全的讨论知道new一个对象的代码是无法保证顺序性的,因此,我们需要使用另一个关键字volatile保证对象实例化过程的顺序性。

public class Singleton {
       private static volatile Singleton instance = null;
       private Singleton() {
       };
       public static synchronized Singleton getInstance() {
              if (instance == null) {
                     synchronized (instance) {
                           if (instance == null) {
                                  instance = new Singleton();
                           }
                     }
              }
              return instance;
       }
}

到此,我们就保证了懒加载的线程安全。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
饿汉线程安全单例模式示例代码: ```cpp class Singleton { public: static Singleton& getInstance() { static Singleton instance; return instance; } private: Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; ``` 在上述代码中,我们将构造函数设为私有,防止外部直接实例化对象。getInstance() 方法是一个静态方法,用于返回单例对象的引用。在其内部,我们使用了静态局部变量的方式来实现单例,这种方式天生就是线程安全的。 懒汉线程安全单例模式示例代码: ```cpp class Singleton { public: static Singleton& getInstance() { std::call_once(initFlag_, []() { instance_ = new Singleton(); }); return *instance_; } private: Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Singleton* instance_; static std::once_flag initFlag_; }; Singleton* Singleton::instance_ = nullptr; std::once_flag Singleton::initFlag_; ``` 在上述代码中,我们同样将构造函数设为私有,防止外部直接实例化对象。getInstance() 方法同样是一个静态方法,用于返回单例对象的引用。在其内部,我们使用了 std::call_once() 函数和 std::once_flag 对象来保证单例对象只被创建一次。在函数内部,我们首先检查 instance_ 是否为空,如果为空,则调用 std::call_once() 函数来保证 instance_ 只被创建一次。由于 std::call_once() 函数是线程安全的,因此这种方式也是线程安全的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

友人和他的朋友们

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值