• 为什么要使用单例?

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

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 对象)。


Allen0323
7 声望1 粉丝

引用和评论

1 篇内容引用
0 条评论