zoukankan      html  css  js  c++  java
  • [设计模式] 设计模式课程(十一)-- 单例模式

    概述

    • 属于“对象性能”模式
    • 面向对象很好地解决了“抽象”的问题,但不可避免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可忽略不计(继承),但某些特殊情况,面向对象带来的成本须谨慎处理(虚函数倍乘效应)
    • 动机:软件系统中有些特殊的类,必须确保它们在系统中只存在一个实例,才能确保逻辑正确及良好的效率
    • 确保一个类仅有一个实例,并提供一个该实例的全局访问点
    • 如何绕过常规构造器,提供一种机制,保证类只有一个实例
    • 类设计者的责任,而不是使用者的责任
    • 拷贝构造函数设计成私有(c++中,缺省构造函数和缺省拷贝构造函数都是共有的)
    • 高并发场景下,锁的代价过大
    • 双检查锁(锁前锁后都检查,防止两个进程前后脚进来),但内存读写reorder不安全,会导致双检查锁失效(线程在指令层次抢时间片,可能与代码顺序不一致)
    • new的默认顺序:分配内存--调用构造器--地址赋值给变量;实际优化后的可能顺序:分配内存--地址赋值给变量--调用构造器(导致线程B拿到了内存地址,而并没有执行构造器)
    • 解决:new过程不能reoder。Java:加volatile声明;C++atomic,memory_order_relaxed/release
    • Singleton模式中的实例构造器可设置为protected以允许子类派生
    • Singleton一般不支持拷贝构造函数和Clone接口,因为有可能导致多个对象实例,与模式初衷违背
    • 双锁检查正确实现,确保多线程环境下安全的Singleton

    场景

    • 一个党只有一个主席
    • Windows中,多个线程同时操作一个文件
    • 一个电脑有两台打印机,输出的时候不能两台打印机打印同一个文件
    • 要求生成唯一序列号
    • web中的计数器,不用每次刷新都在数据库里加一次,而是用单例缓存起来
    • 创建一个对象需要消耗的资源过多,如I/O与数据库的连接

    实现

    • 构造函数权限是private
    • 考虑线程安全
    • 考虑是否支持延迟加载
    • 考虑getInstance性能(是否加锁)
    • 饿汉式
      • 类加载时,instance静态实例已创建并初始化
      • 不支持延迟加载(等真正用到时再创建实例)
    • 懒汉式
      • 给getInstance()加锁
      • 支持延迟加载
      • 不支持高并发
    • 双重检测
      • IdGenerator 
      • 支持延迟加载和高并发
    • 静态内部类
      • 支持延迟加载和高并发
    • 枚举

    示例1(c++)

     1 class Singleton{
     2 private:
     3     Singleton();
     4     Singleton(const Singleton& other);
     5 public:
     6     static Singleton* getInstance();
     7     static Singleton* m_instance;
     8 };
     9 
    10 Singleton* Singleton::m_instance=nullptr;
    11 
    12 //线程非安全版本
    13 Singleton* Singleton::getInstance() {
    14     if (m_instance == nullptr) {
    15         m_instance = new Singleton();
    16     }
    17     return m_instance;
    18 }
    19 
    20 //线程安全版本,但锁的代价过高
    21 Singleton* Singleton::getInstance() {
    22     Lock lock;
    23     if (m_instance == nullptr) {
    24         m_instance = new Singleton();
    25     }
    26     return m_instance;
    27 }
    28 
    29 //双检查锁,但由于内存读写reorder不安全
    30 Singleton* Singleton::getInstance() {
    31     
    32     if(m_instance==nullptr){
    33         Lock lock;
    34         if (m_instance == nullptr) {
    35             m_instance = new Singleton();
    36         }
    37     }
    38     return m_instance;
    39 }
    40 
    41 //C++ 11版本之后的跨平台实现 (volatile)
    42 std::atomic<Singleton*> Singleton::m_instance;
    43 std::mutex Singleton::m_mutex;
    44 
    45 Singleton* Singleton::getInstance() {
    46     Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    47     std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    48     if (tmp == nullptr) {
    49         std::lock_guard<std::mutex> lock(m_mutex);
    50         tmp = m_instance.load(std::memory_order_relaxed);
    51         if (tmp == nullptr) {
    52             tmp = new Singleton;
    53             std::atomic_thread_fence(std::memory_order_release);//释放内存fence
    54             m_instance.store(tmp, std::memory_order_relaxed);
    55         }
    56     }
    57     return tmp;
    58 }
    View Code

    示例2(java)

     1 public class SingletonLazy {
     2     public static void main(String[] args) {
     3         President zt1 = President.getInstance();
     4         zt1.getName();
     5         President zt2 = President.getInstance();
     6         zt2.getName();
     7         if(zt1==zt2) {
     8             System.out.println("他们是同一人!");
     9         }else {
    10             System.out.println("他们不是同一人!");
    11         }
    12     }
    13 }
    14 
    15 class President{
    16     private static volatile President instance = null;
    17     
    18     private President() {
    19             System.out.println("产生一个总统!");
    20     }
    21     
    22     public static synchronized President getInstance() {
    23         if(instance==null) {    
    24             instance=new President();
    25         }
    26         else {
    27             System.out.println("已经有一个总统,不能产生新总统!");
    28         }
    29         return instance;
    30     }
    31     
    32     public void getName() {
    33         System.out.println("我是美国总统:特朗普");
    34     }
    35 }
    View Code
  • 相关阅读:
    利用Cubieborad破解WiFi
    从零开始——Mysql备份还原数据库
    从零开始——Ubuntu系统安装redis和phpredis
    监控应用卡顿BlockCanary
    2.AS内存分析
    热修复原理
    MultiDex 原理
    APP启动时白屏优化及multidex优化
    线程池的简便记忆方法
    2.volatile和AtomicXX
  • 原文地址:https://www.cnblogs.com/cxc1357/p/12310649.html
Copyright © 2011-2022 走看看