Appearance
单例模式同时解决了两个问题, 所以违反了单一职责原则,如今, 单例模式已经变得非常流行, 以至于人们会将只解决下文描述中任意一个问题的东西称为单例。
- 保证一个类只有一个实例:为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象,客户端甚至可能没有意识到它们一直都在使用同一个对象。
- 为该实例提供一个全局访问节点: 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。
使用场景
- 业务系统全局只需要一个对象实例,比如发号器、redis连接对象等
- SpringIOC容器中的bean默认就是单例
- SpringBoot 中的controller、service、dao层中通过@autowire的依赖注入对象默认都是单例的
- 如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式
- 如果你需要更加严格地控制全局变量,可以使用单例模式
解决方案
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有, 防止其他对象使用单例类的
new
运算符。 - 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
此外,单例的实现还分为两种,懒汉式与饿汉式。
- 懒汉:就是所谓的懒加载,延迟创建对象
- 饿汉:与懒汉相反,提前创建对象
真实世界类比
政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。
初探懒汉实现方式
java
public class SingletonLazy {
private static SingletonLazy instance;
/**
* 单例对象对外开放的方法
*/
public void process(){
System.out.println("方法调用成功");
}
/**
* 构造函数私有化
*/
private SingletonLazy(){}
/**
* DCL 双重检查锁定 可以在多线程情况下保持高性能
*
* 这是否安全,instance = new SingletonLazy(); 并不是原子性操作
* 1、分配空间给对象
* 2、在空间内创建对象
* 3、将对象赋值给引用instance
*
* 假如线程 1-》3-》2顺序,会把值写会主内存,其他线程就会读取到instance最新的值,但是这个是不完全的对象
* (指令重排)
* @return
*/
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
// /**
// * 第三种方式,减小锁的力度
// * @return
// */
// public static SingletonLazy getInstance(){
// if (instance == null){
// synchronized(SingletonLazy.class){
// instance = new SingletonLazy();
// }
// }
// return instance;
// }
// /**
// * 第二种
// * 通过枷锁保证单例 synchronized
// * 问题:高并发场景下影响效率
// * @return
// */
// public static synchronized SingletonLazy getInstance(){
// if (instance == null){
// instance = new SingletonLazy();
// }
// return instance;
// }
// /**
// * 第一种实现方式
// * 对外暴露一个方法获取类的对象
// *
// * 线程不安全,多线程下存在安全问题
// */
// public static SingletonLazy getInstance(){
// if (instance == null){
// instance = new SingletonLazy();
// }
// return instance;
// }
}
调用
java
public static void main(String[] args) {
//调用成功
SingletonLazy.getInstance().process();
//new SingletonLazy(); 报错,单例模式只允许调用对外开放的方法
}
懒汉最终实现+双重检查锁定+内存模型
java
public class SingletonLazy {
//volatile是java提供的关键词,可以禁止指令重排
private static volatile SingletonLazy instance;
/**
* 最终实现:双重检查锁定+内存模型
* @return
*/
public static SingletonLazy getInstance() {
//第一重检查
if (instance == null) {
//锁定
synchronized (SingletonLazy.class) {
//第二重检查
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
/**
* 单例对象对外开放的方法
*/
public void process() {
System.out.println("方法调用成功");
}
/**
* 构造函数私有化
*/
private SingletonLazy() {}
饿汉实现
java
/**
* Description: 单例设计模式 - 饿汉式实现
* date: 2021/1/24 21:26
* @author CV大魔王
*/
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry(){}
public SingletonHungry getInstance(){
return instance;
}
//单例对象调用方法
public void process(){
System.out.println("方法调用成功");
}
}
懒汉式和饿汉式的选择
饿汉⽅式:提前创建好对象
- 优点:实现简单,没有多线程同步问题
- 缺点:不管有没使⽤,instance对象⼀直占着这段内存
如何选择:
- 如果对象不⼤,且创建不复杂,直接⽤饿汉的⽅式即可
- 其他情况则采⽤懒汉实现⽅式