单例模式( 二 )


优点:比直接加锁效率高 。
缺点:内存读写reorder不安全 。
reorder问题:一般new的执行过程认为是分配内存->构造函数初始化->返回地址,但是实际上可能是分配内存->返回地址->构造函数这种错乱的顺序 。如果线程1reorder,另外一个线程2判断非空直接返回pSingleton,但是这个对象实例是无法正常使用的,它只是一个还未调用构造函数初始化的内存 。
所以编译器需要解决这类问题,即编译器不能优化 。
3)volatile
std::atomic<Singleton*> Singleton::m_instance;std::mutex Singleton::m_mutex;Singleton* Singleton::getInstance() {Singleton* tmp = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acuire);//获取内存fenceif (nullptr == tmp) {tmp = new Singleton;std::atomic_thread_fence(std::memory_order_release);//释放内存fencem_instance.store(tmp,std::memory_order_relaxed);}return tmp;}volatile这个关键字有两层语义:
第一层语义是可见性 。可见性指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中,即看到的都是最新的结果 。
第二层语义是禁止指令重排序优化 。我们写的代码(尤其是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同 。
3.3 静态内部类
有没有一种延时加载,并且能保证线程安全的简单写法呢?我们可以把Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的:
public class Singleton {private static class Holder {private static Singleton singleton = new Singleton();}private Singleton(){}public static Singleton getSingleton(){return Holder.singleton;//静态类方式获取实例}}3.4 选择
单例模式懒汉模式饿汉模式概念在类加载时不创建实例,采用延迟加载的方式,在运行调用时创建实例在类加载的时候,就完成初始化特点类加载速度快,但是运行时获取对象的速度较慢(时间换空间) 。类加载较慢,但获取对象速度快(空间换时间) 。延迟加载具备不具备线程安全线程不安全线程安全一般采用饿汉式,若对资源十分在意可以采用静态内部类,不建议采用懒汉式及双重检测 。
04特点4.1 优点1、提供了对唯一实例的受控访问 。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它 。
2、只存在一个对象,节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能 。
4.2 缺点1、由于单例模式中没有抽象层,因此单例类的扩展有很大的困难 。
2、单例类的职责过重,在一定程度上违背了“单一职责原则” 。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起 。
3、滥用单例将带来一些负面问题 。现在很多面向对象语言(如Java)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失 。
05应用场景在以下情况下可以使用单例模式:1、系统只需要一个实例对象(线程池、缓存、硬件设备等),或者需要考虑资源消耗太大而只允许创建一个对象 。

推荐阅读