在设计模式中,最为常见常用的应该就是单例模式了。
单例模式的使用场景:
1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
2.控制资源的情况下,方便资源之间的互相通信。如线程池等。
单例模式要素:
- 私有构造方法
- 私有静态成员变量,自身类型的实例
- 以自己实例为返回值的公有静态方法
单例模式常见的七种实现方式
——
1. 简单的懒汉模式 非线程安全
public class Sigleton1 {
private static Sigleton1 instance;
private Sigleton1() {
}
public static Sigleton1 getInstance() {
if (instance == null) { //代码1 多线程环境下,判断是否为null这里是非线程安全的
instance = new Sigleton1(); //代码2
}
return instance;
}
}
假设线程A先判断 代码1 发现 instance 为 null 然后执行 代码2,此时线程B 执行代码1 但是线程A还没有完成对象的初始化,instance 此时为 null ,因此线程B 也会执行代码2 ,所以这不是线程安全的方法。
——
2. 采用同步的懒汉模式 线程安全
public class Sigleton2 {
private static Sigleton2 instance;
private Sigleton2() {
}
public synchronized static Sigleton2 getInstance() {
if (instance == null) {
instance = new Sigleton2();
}
return instance;
}
}
在 getInstance() 方法加同步,线程安全性得到了保证,但是如果在多个线程频繁调用该方法的场景下,程序执行的性能会因为这里的同步而大幅下降。
——
3. 简单双重校验 非线程安全
采用双重校验理论上降低了同步的开销
public class Sigleton3 {
private static Sigleton3 instance;
private Sigleton3() {
}
public static Sigleton3 getInstance() {
if (instance == null) {
synchronized (Sigleton3.class) {
if (instance == null) {
instance = new Sigleton3();
}
}
}
return instance;
}
}
编译器提示安全问题:
原因是:
instance = new Sigleton3();
创建一个对象的过程可以分解为:
memory = allocate(); //1: 分配对象的内存空间
ctorInstance(memory); //2: 初始化对象
instance = memory; //3: 设置 instance 指向刚分配的内存地址
然而 代码2 和 代码3 的顺序可能会被编译器(如JIT)优化重排序,从而变成下面的执行顺序:
memory = allocate(); //1: 分配对象的内存空间
instance = memory; //3: 设置 instance 指向刚分配的内存地址
//注意对象还没有被初始化
ctorInstance(memory); //2: 初始化对象
时间 | 线程A | 线程B |
---|---|---|
t1 | A1:分配对象的内存空间 | |
t2 | A3:设置instance指向内存空间 | |
t3 | B1:判断instance是否为空 | |
t4 | B2:instance != null, 线程B将访问instance引用的对象 | |
t5 | A2:初始化对象 | |
t6 | A4:访问instance引用的对象 |
因此,上述的简单双重校验是危险的非线程安全实现方式。
——
4. 带volatile 的双重校验 线程安全
想把非线程安全的双重校验改成线程安全的双重校验有两种方式:
(1)禁止 2 和 3 的重排序
(2)允许 2 和 3 的重排序,但不允许其他线程“看到” 这个重排序。
基于上述第一个方法,只需要对实现3的简单实现做简单修改,对instance加上volatile即可:
public class Sigleton3 {
private volatile static Sigleton3 instance;
// volatile 禁止对volatile写和volatile写前面任意内存操作重排序
// volatile 禁止对volatile读和volatile读后面任意内存操作重排序
// volatile 将修改后的值立刻刷新到内存,保证内存可见性
private Sigleton3() {
}
public static Sigleton3 getInstance() {
if (instance == null) {
synchronized (Sigleton3.class) {
if (instance == null) {
instance = new Sigleton3();
}
}
}
return instance;
}
}
——
5. 饿汉模式 线程安全
public class Sigleton5 {
private static Sigleton5 instance = new Sigleton5();
private Sigleton5() {}
public static Sigleton5 getInstance() {
return instance;
}
}
因为没有重复判断 instance 所以不存在线程安全性问题。
——
6. 基于类初始化的解决方案——静态内部类 线程安全 推荐
public class Sigleton6 {
private Sigleton6() { }
private static class SigletonHolder {
private static final Sigleton6 instance = new Sigleton6();
}
public static Sigleton6 getInstance() {
return SigletonHolder.instance;
}
}
——
7. 基于枚举的单例模式 线程安全
public enum Sigleton {
instance;
}
Over,再也不怕面试官让手撸单例模式了!