-
单例设计模式:
- 单例的意思只包含一个对象被称为单例的特殊类
- 通过单例设计模式可以保证系统中,应用该模式的类只有一个对象
-
使用场景
- 业务系统全局只需要一个对象实例,比如发号器功能
- Spring IOC容器中的bean默认就是单例
- springboot中的controller、service、dao层中通过@autowire的依赖注入对象默认都是单例的
-
单例设计模式分类
- 懒汉:就是所谓的懒加载,延迟创建对象
- 饿汉:与懒汉相反,提前创建对象
-
单例设计模式大体实现步骤
- 私有化构造方法
- 提供获取单例的方法
1. 单例懒汉式(线程不安全)
/**
* 懒汉单例设计模式
*/
public class SingletonLazy {
private static SingletonLazy instance;
/**
* 构造函数私有化
*/
private SingletonLazy() {}
/**
* 第一种方式
* 对外暴露一个方法获取对象的类
* 问题:多线程情况下,无法保证这个对象唯一,线程不安全
* @return
*/
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
比如线程1运行到if(instance == null)这行,还没运行到下一行创建instance 对象时,线程2也运行到这个判断条件,
此时线程1,2对instance 读取的值都为null,故而他俩都会去创建类的实例,这样无法保证唯一实例。
2. 单例懒汉式(线程安全synchronized)
/**
* 懒汉单例设计模式
*/
public class SingletonLazy {
private static SingletonLazy instance;
/**
* 构造函数私有化
*/
private SingletonLazy() {}
public void process() {
System.out.println("方法调用成功");
}
/**
* 第二种方式
* 通过加锁 synchronized 保证单例
* 采⽤用synchronized 对⽅方法加锁有很⼤大的性能开销
* 线程线粒度较大
* @return
*/
public static synchronized SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
这样给整个方法都加上了同步锁,即每次想得到一次单例都会给此方法加锁,这样使线程之间退化成串行化的执行。上面方法效率太低
3. 单例懒汉模式(双重检查锁定)
/**
* 懒汉单例设计模式
*/
public class SingletonLazy {
private static SingletonLazy instance;
/**
* 构造函数私有化
*/
private SingletonLazy() {}
public void process() {
System.out.println("方法调用成功");
}
/**
* 第三种方式
*
* DCL 双重检查锁定 (Double-Checked-Locking),在多线程情况下保持⾼高性能
* @return
*/
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
双重检查锁定模式也有一定的风险,由于instance = new
SingletonLazy(); 并不不是原⼦子性操作。
执行这一行代码,经历了一下步骤
- 分配空间给对象
- 在空间内创建对象
- 将对象赋值给instance
假如多线程环境下,以1->3->2的顺序,会把值写回主内存,其他线程就会读取到instance最新的值,但是这个是不完全的对象(指令重排)
什么是指令重排?指令重排是指执行代码的顺序和编写代码不一致,即虚拟机优化代码顺序,编译或运行时环境为了优化程序性能而采取的对指令进行重写排序执行的一种手段。如果两个操作访问一个变量,而且这两个操作中有一个为写操作,此时这两个操作之间存在数据依赖(简单说就是这两个操作若互换顺序了,就会带来不同的结果)。编译器和处理器重排指令顺序时候,会遵守数据依赖,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
4. 单例懒汉设计模式(双重锁+禁止指令重排)
/**
* 懒汉单例设计模式
*/
public class SingletonLazy {
/**
* 构造函数私有化
*/
private SingletonLazy() {}
public void process() {
System.out.println("方法调用成功");
}
/**
* volatile是Java提供的关键字,它具有可⻅见性和有序性
*
*
* 指令重排序是JVM对语句句执⾏行行的优化,只要语句句间没有
* 依赖,那JVM就有权对语句句进⾏行行优化
* @return
*/
private static volatile SingletonLazy instance;
public static SingletonLazy getInstance() {
// 第一重检查
if (instance == null) {
// 锁定
synchronized (SingletonLazy.class) {
// 第二重检查
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
5. 饿汉式单例设计模式
/**
* 饿汉单例设计模式
*/
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry(){}
public SingletonHungry getInstance(){
return instance;
}
}
- 饿汉方式:提前创建好对象
- 优点:实现简单,没有多线程同步问题
- 缺点:不管有没有用,instance对象一直占着这段内存
- 如何选择:如果对象不大,且创建方式不复杂,直接用饿汉的方式即可,其他采用懒汉实现方式