乐趣区

设计模式2-单例模式

  • 为什么要使用单例?

单例存在哪些问题?单例与静态类的区别?有何替代的解决方案?

1. 命令模式介绍

1.1 定义

单例设计模式(Singleton Design Pattern): 一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

  • 命令模式的本质:封装请求
  • 设计意图:命令模式通过将请求封装到一个命令 (Command) 对象中,实现了请求调用者和具体实现者之间的解耦。

1.2 应用场景

为什么要使用单例?单例存在哪些问题?单例与静态类的区别?有何替代的解决方案?

2. 示例

2.1 实战案例一:处理资源访问冲突

代码如下:


public class Logger {
  private FileWriter writer;
  
  public Logger() {File file = new File("/Users/wangzheng/log.txt");
    writer = new FileWriter(file, true); //true 表示追加写入
  }
  
  public void log(String message) {writer.write(mesasge);
  }
}

// Logger 类的应用示例:public class UserController {private Logger logger = new Logger();
  
  public void login(String username, String password) {
    // ... 省略业务逻辑代码...
    logger.log(username + "logined!");
  }
}

public class OrderController {private Logger logger = new Logger();
  
  public void create(OrderVo order) {
    // ... 省略业务逻辑代码...
    logger.log("Created an order:" + order.toString());
  }
}
  • 如上日志打印代码,势必会导致文件覆盖。
  • 如果加对象锁,占用内存等;
  • 单例模式相对于之前类级别锁的好处是,不用创建那么多 Logger 对象 一方面节省内存空间 另一方面节省系统文件句柄(对于操作系统来说,文件句柄也是一种资源,不能随便浪费)。

2.2 实战案例二:表示全局唯一类

  • 从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。
  • 比如,配置信息类。在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所应当只有一份。

3. 单例模式实现

实现单例模式,需要注意的点:

1. 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;2. 考虑对象创建时的线程安全问题;3. 考虑是否支持延迟加载;4. 考虑 getInstance() 性能是否高(是否加锁)。

将该类的构造函数私有化,目的是 禁止其他程序创建该类的对象 ,同时也是为了提醒查看代码的人我这里是在使用单例模式, 防止他人将这里任意修改。此时,需要提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式)。

3.1 懒汉 模式:

懒汉:第一次用到类的实例的时候才回去实例化。

  class singleton   // 实现单例模式的类  
  {  
      private:  
          singleton(){}  // 私有的构造函数  
          static singleton* Instance;  
      public:  
          static singleton* GetInstance()  
          {if (Instance == NULL) // 判断是否第一调用  
                  Instance = new singleton();  
              return Instance;  
          }  
  }; 

缺点:这个实现在单线程下是正确的,但在多线程情况下,如果两个线程同时首次调用 GetInstance方法且同时检测到 Instance 是 NULL,则两个线程会同时构造一个实例给 Instance,这样就会发生错误。

3.2. 改进的懒汉式(双重检查锁)

思路:只有在第一次创建的时候进行加锁,当 Instance 不为空的时候就需要进行加锁的操作。代码如下:

  class singleton   // 实现单例模式的类  
  {  
  private:  
      singleton(){}  // 私有的构造函数  
      static singleton* Instance;  
        
  public:  
      static singleton* GetInstance()  
      {if (Instance == NULL) // 判断是否第一调用  
          {Lock(); // 表示上锁的函数  
              if (Instance == NULL)  
              {Instance = new singleton();  
              }  
              UnLock() // 解锁函数}             
          return Instance;  
      }  
  };  

3.3 饿汉 模式:

饿汉式的特点是 一开始就加载了 ,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”, 因为一开始就创建了实例,所以每次用到的之后直接返回就好了。饿汉模式是线程安全的

 class singleton   // 实现单例模式的类  
 {  
 private:  
     singleton() {}  // 私有的构造函数  
       
 public:  
     static singleton* GetInstance()  
     {  
         static singleton Instance;  
         return &Instance;  
     }  
 }; 

3.4 通过 config 来实现单例模式构造函数参数

代码如下:


public class Config {
 public static final int PARAM_A = 123;
 public static final int PARAM_B = 245;
}

public class Singleton {
 private static Singleton instance = null;
 private final int paramA;
 private final int paramB;

 private Singleton() {
   this.paramA = Config.PARAM_A;
   this.paramB = Config.PARAM_B;
 }

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

3.5 如何理解单例模式中的唯一性?

全局可见的唯一类对象,指的时进程中保证唯一性。

进程之间是不共享地址空间的,如果我们在一个进程中创建另外一个进程(比如,代码中有一个 fork() 语句,进程执行到这条语句的时候会创建一个新的进程),操作系统会给新进程分配新的地址空间,并且将老进程地址空间的所有内容,重新拷贝一份到新进程的地址空间中,这些内容包括代码、数据(比如 user 临时变量、User 对象)。

退出移动版