懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {  
private static Singleton singleton;

private Singleton(){}

public static Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}

饿汉式

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

双重校验锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Singleton3 {  
// 创建一个对象,在JVM中会经过三步:
// (1)为singleton分配内存空间
// (2)初始化singleton对象
// (3)将singleton指向分配好的内存空间
//在这个过程中2、3步可能发生指令重排序,因此需要使用volatile来修饰
private static volatile Singleton3 singleton3;

private Singleton3(){}

public static Singleton3 getInstance() {
// 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
if (singleton3 == null) {
// 线程A或线程B获得该锁进行初始化
synchronized(Singleton3.class) {
// 其中一个线程进入该分支,另外一个线程则不会进入该分支
if (singleton3 == null) {
singleton3 = new Singleton3();
}
}
}
return singleton3;
}
}

枚举

1
2
3
4
public enum Singleton4 {  
INSTANCE;
Singleton4() { System.out.println("枚举创建对象了"); }
}

单例模式的线程安全问题

在一般的单例模式实现中,如果没有特殊处理,是不具备线程安全性的。主要的问题在于多线程环境下可能会导致多个线程同时访问到未初始化完成的实例或者同时执行实例的创建操作,从而造成实例的不一致性或错误。
上述四种单例模式的实现中,饿汉式、双重校验锁、枚举都可以保证单例的线程安全

  1. 饿汉式在类加载时就创建了实例,因此不会存在多线程环境下的竞争问题。但它可能会提前占用资源,因为无论是否使用,实例都会被创建
  2. 双重校验锁使用volatile关键字确保多线程环境下对instance变量的可见性,而双重检查锁定通过在锁内外都进行一次判空来提高性能
  3. 枚举类型在Java中天生就是单例的,且线程安全