设计模式篇之除了你之外我都不要

这篇文章是学习单例模式的笔记总结,主要内容包括了懒汉式,饿汉式,双重校验锁,枚举,静态内部类五种单例模式,强烈推荐使用原生记事本默写一遍。

懒汉式

1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}

特点:lazy,非线程安全,简单

饿汉式

1
2
3
4
5
6
7
public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return singleton;
}
}

特点:not lazy,线程安全,简单高效,但类加载时就初始化,即使暂不需要,浪费内存

双重校验锁(Double-checked locking)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance() { // 代码1
if (singleton == null) {
synchronized(Singleton.class) {
if (singleton == null) {
singleton = new Singleton(); // 代码2
}
}
}
return singleton;
}
}

特点:Lazy,线程安全,多线程下能保持高性能

为什么需要volatile?

加volatile关键字生成的汇编指令会多一个lock前缀指令,相当于一个内存屏障。

  • 禁止进行指令重排序。
  • 保证了不同线程对该变量操作的可见性。具体来说是强制将对缓存的修改立即写入主存,
    如果是写操作,它会导致其他CPU中对应的缓存行无效。

为什么需要双重if校验?

1
singleton = new Singleton(); // 代码2

上面这行可以分解为三个操作:

1
2
3
1. memory = allocate(); // 分配内存
2. ctorInstance(memory); // 初始化对象
3. instance = memory; // 设置instance指向刚分配的地址

如果没有加入volatile关键字,可能会出现指令重排序1-2-31-3-2两种情况
对于第2种情况,线程A,B都进入getInstance方法后,线程A获得锁,执行了1,3,此时B判断instance不为null,直接返回未初始完成的对象,就会出现问题。因此需要用volatile保证指令重排序。

枚举类型

1
2
3
4
5
6
7
public enum Singleton {

SINGLETON;
public void whateverMethod(){

}
}

特点:not Lazy,线程安全,实现简单,不存在反射和反序列化漏洞

静态内部类(static nested class)

1
2
3
4
5
6
7
8
9
public class Singleton{
private Singleton(){}
private static class Inner{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton(){
return Inner.INSTANCE;
}
}

特点:Lazy,线程安全,实现相比双重校验锁简单
引申:静态内部类与内部类区别,内存泄漏
静态内部类等同于外部类的静态方法,只能访问外部类的静态变量和静态方法。