饿汉模式

class

1
2
3
4
5
6
7
8
9
10
11
// 饿汉模式
public final class Singleton {
private static Singleton instance = new Singleton();

private Singleton() {
}

public static Singleton getInstance() {
return instance;
}
}
  1. 使用了static修饰了成员变量instance,所以该变量会在类初始化的过程中被收集进_类构造器<clinit>_
  2. 多线程场景下,JVM会保证只有一个线程能够执行该类的<clinit>方法,其它线程将会被阻塞等待
    • 等到唯一的一次<clinit>方法执行完成后,其它线程将不会再执行<clinit>方法,转而执行自己的代码
    • 因此,static修饰的成员变量instance,在多线程的情况下能保证只实例化一次
  3. 类初始化阶段就已经在堆内存中开辟了一块内存,用于存放实例化对象,所以也称为饿汉模式
    • 可以保证多线程情况下实例的唯一性,而且getInstance直接返回唯一实例,性能很高
    • 在类成员变量比较多,或者变量比较大的情况下,这种模式可能会在没有使用类对象的情况下,一直占用堆内存
    • 第三方框架一般不会采用饿汉模式来实现单例模式

enum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 饿汉模式 + 枚举实现
public enum Singleton {
INSTANCE;

public List<String> list;

// 默认构造函数
Singleton() {
list = new ArrayList<>();
}

public static Singleton getInstance() {
return INSTANCE;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// javap -v
public static final Singleton INSTANCE;
descriptor: LSingleton;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class Singleton
3: dup
4: ldc #11 // String INSTANCE
6: iconst_0
7: invokespecial #12 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #10 // Field INSTANCE:LSingleton;
13: iconst_1
14: anewarray #4 // class Singleton
17: dup
18: iconst_0
19: getstatic #10 // Field INSTANCE:LSingleton;
22: aastore
23: putstatic #1 // Field $VALUES:[LSingleton;
26: return
LineNumberTable:
line 6: 0
line 5: 13
  1. Java中的enum是一种语法糖!!,在javac编译后,枚举类中的枚举域会被声明为static属性
  2. 并且枚举域的初始化会被收录进静态代码块static{},类初始化时会被收录进<clinit>,由JVM保证线程安全

懒汉模式

Double-Check

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 懒汉模式
public final class Singleton {
private static Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
  1. 懒汉模式是为了避免加载类对象时提前创建对象的一种单例模式
  2. 懒汉模式使用了懒加载方式,只有当系统使用到类对象时,才会将实例加载到堆内存中
  3. 上面的代码在多线程环境下,可能会出现实例化多个类对象的情况
    • 当线程A进入到if判断条件后,开始实例化对象,此时instance依然为null
    • 又有线程B进入到if判断条件,之后也会通过判断条件,进入到方法又创建一个实例
    • 可以对方法进行加锁,保证多线程下仅创建一个实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 懒汉模式 + synchronized同步锁
public final class Singleton {
private static Singleton instance = null;

private Singleton() {
}

public static synchronized Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
  1. 同步锁会增加锁竞争,带来系统性能开销,从而导致系统性能下降,因此这种方式也会降低单例模式的性能
  2. 可以考虑将同步锁放在if条件里面,减少锁粒度,进而减少同步锁的资源竞争
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 懒汉模式 + synchronized同步锁
public final class Singleton {
private static Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
  1. 上述代码依然会创建多个实例,因为在多线程并发情况下,可以同时通过if判断条件
  2. 因此需要在同步锁里面再加一个判断条件,即Double-Check
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 懒汉模式 + synchronized同步锁 + Double-Check
public final class Singleton {
private static Singleton instance = null;
public List<String> list;

private Singleton() {
list = new ArrayList<>();
}

public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
  1. 在JVM中,重排序是非常重要的一环,尤其在并发编程中,JVM可能会进行任意排序以提高程序性能
  2. 上面代码instance = new Singleton()的执行过程
    1. 给Singleton分配内存
    2. 调用Singleton的构造函数来初始化成员变量(即list)
    3. 将Singleton对象指向分配的内存空间(执行完之后instance为非null
  3. 如果JVM发生重排序优化,上面的步骤3可能会发生在步骤2之前
    • 如果初始化线程A刚好完成步骤3,而步骤2没有进行时,又有另一个线程B到了第一次判断
    • 线程B判断instance为非null,直接返回对象(未完成构造)使用,可能会导致异常
  4. synchronized只能保证可见性、原子性,但无法保证同步块内执行的顺序!!
  5. volatile可以保证线程间变量的可见性,同时还可以阻止局部重排序的发生,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 懒汉模式 + synchronized同步锁 + Double-Check
public final class Singleton {
private volatile static Singleton instance = null;
public List<String> list;

private Singleton() {
list = new ArrayList<>();
}

public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}

内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 懒汉模式 + 内部类实现
public final class Singleton {
public List<String> list;

private Singleton() {
list = new ArrayList<>();
}

public static class InnerSingleton {
private static Singleton instance = new Singleton();
}

public static Singleton getInstance() {
return InnerSingleton.instance;
}
}

enum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 懒汉模式 + 内部类 + 枚举实现
public class Singleton {
public List<String> list;

private Singleton() {
list = new ArrayList<>();
}

private enum EnumSingleton {
INSTANCE;
private Singleton instance;

EnumSingleton() {
instance = new Singleton();
}

public Singleton getSingleton() {
return instance;
}
}

public static Singleton getInstance() {
return EnumSingleton.INSTANCE.getSingleton();
}
}

参考资料

Java性能调优实战