共计 2834 个字符,预计需要花费 8 分钟才能阅读完成。
- 为什么要使用单例?
单例存在哪些问题?单例与静态类的区别?有何替代的解决方案?
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 对象)。