1. What?
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
单例模式又分 懒汉式
与 饿汉式
:
懒汉式: 在类加载时不会初始化,而在需要使用时进行实例化。
优点是初始化时减少内存空间,在并发下可能存在的问题,代码中会详细介绍并发时如何避免
饿汉式: 在类加载时实例化。
不会存在并发问题,不过初始化时占用了内存(这点内存可以忽略不计,推荐饿汉式单例)
2. Why?
-
单例类只能有一个实例。
-
单例类必须自己创建自己的唯一实例。
-
单例类必须给所有其他对象提供这一实例。
在不需要创建新对象时,避免创建不必要的内存空间,可以使用单例模式实例化对象
3. How?
1. 饿汉式
/**
* 饿汉式
*/
public class HungrySingle {
private final static HungrySingle INSTANCE=new HungrySingle();
private HungrySingle(){}
public static HungrySingle getInstance(){
return INSTANCE;
}
}
枚举
,枚举默认是单例的,原因: 传送门
/**
* 饿汉式(枚举)
*/
public enum Single {
INSTANCE
}
2. 懒汉式
a. 为什么用synchronized?b. 为什么双重判空? c. 为什么要用volatile修饰?
/**
* 懒汉式
*/
public class LazySingle {
// volatile作用:
// a. 避免指令重排序;
// b. 保证变量可见性,不能保证原子性
// c. 内存屏蔽
private static volatile LazySingle instance;
private LazySingle() {}
// 前提:多线程情况下
// 为了保证线程安全,使用同步锁机制和双重判空逻辑
public static LazySingle getInstance() {
// 第一层判空逻辑,拦截已经初始化的调用
if (instance == null) {
// 添加同步锁机制,即多线程进入代码,排队等待执行
synchronized (LazySingle.class) {
// 第二层判断的原因:多线程进入,可能存在两个线程同时执行第一层判空都进入代码块,则需要再次判断是否为空,若没有判空逻辑,则会出现创建多个实例的问题
if (instance == null) {
// jvm层面创建对象过程:1. 开辟堆上内存空间,-> 2. 调用构造方法,-> 3. 分配指针指向实例对象的内存地址,即非原子操作
// 由于编译优化,2、3操作没有依赖关系,可能执行顺序1->3->2,也可能是1->2->3,不过volatile可避免指令重排序,只允许顺序执行:1->2->3
// 不添加volatile在单线程情况下 1->3->2 执行没问题,不过在多线程下可能会有问题
// 比如线程A执行1操作时,时间片用完,这时线程B进入,执行1->3,时间片用完,线程A继续执行,发现不为空,直接返回,其实还没有执行构造方法,对象还是没有实例化
instance = new LazySingle();
}
}
}
return instance;
}
}