- 为什么要使用单例?
单例存在哪些问题?单例与静态类的区别?有何替代的解决方案?
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 对象)。