类延迟初始化常采用单例模式。
方法1:1
2
3
4
5
6
7
8
9
10public class Singleton {
private static Singleton singleton = null;
public static Singleton getInstance() {
if (singleton == null) {//1
singleton=new Singleton();//2
}
return singleton;
}
}
多线程中,步骤1可以同步执行,导致步骤2多次执行。
方法2:1
2
3
4
5
6
7
8
9
10public class Singleton {
private static Singleton singleton = null;
public static synchronized Singleton getInstance() {
if (singleton == null) {//1
singleton=new Singleton();//2
}
return singleton;
}
}
多线程中,singleton只会实例一次,但每次获取getInstance都会加锁导致性能下降。
方法3:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Singleton {
private static Singleton singleton = null;
public static Singleton getInstance() {
//one check
if (singleton == null) {//1
synchronized (Singleton.class) {//2
//two check
if (singleton == null) {//3
singleton = new Singleton();//4
}
}
}
return singleton;
}
}
步骤4分解为3条指令执行,包括1-先分配内存,2-标记对象指向内存地址,3-初始化构造;如果指令12重排导致先赋值,多线程并发中步骤1不为null,但获取的singleton仍未初始化。
方法4:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Singleton {
//防止重排
private volatile static Singleton singleton = null;//5
public static Singleton getInstance() {
//one check
if (singleton == null) {//1
synchronized (Singleton.class) {//2
//two check
if (singleton == null) {//3
singleton = new Singleton();//4
}
}
}
return singleton;
}
}
步骤5使用volatile防止指令重排,是标准的DCL实例方案。
方法5: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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48public class Singleton implements Serializable{//序列化
//防止重排
private volatile static Singleton singleton = null;//5
private static boolean flag = false;
private Singleton() {//6
//防止反射漏洞
synchronized (Singleton.class) {
if (flag) {
System.out.println("init twice");
} else {
flag = true;
System.out.println("init once");
}
}
}
public static Singleton getInstance() {
//one check
if (singleton == null) {//1
synchronized (Singleton.class) {//2
//two check
if (singleton == null) {//3
singleton = new Singleton();//4
}
}
}
return singleton;
}
//防止反序列化漏洞,替换反序列化对象
private Object readResolve() {
return singleton;
}
public static void main(String[] args) throws Exception {
Class<Singleton> clazz = (Class<Singleton>) Class.forName("Singleton");
Constructor<Singleton> singletonConstructor = clazz.getDeclaredConstructor(null);
singletonConstructor.setAccessible(true);//反射打开访问权限
Singleton singleton = singletonConstructor.newInstance();//第一次实例化
singleton.say();
System.out.println(singleton);
singleton = singletonConstructor.newInstance();//第二次实例化
singleton.say();
}
}
步骤6对构造函数进行私有化,防止多次被实例化;同时,为防止反射多次实例,使用全局变量flag;也支持单例模式的序列化和反序列化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
方法6:1
2
3
4
5
6
7
8
9
10
11public class Singleton {
private static Singleton singleton = null;
private static class SingletonFactory {
public static Singleton singleton2 = new Singleton();
}
public static Singleton getInstance() {
return SingletonFactory.singleton2;
}
}
利用类加载机制来实现,延迟初始化,类加载分为加载->验证->准备->解析->初始化->使用->卸载等步骤,Java中当类的静态域或静态方法被引用的时候,必须对声明这个静态域或方法的类进行初始化。
方法7:1
2
3
4
5
6
7
8
9
10
11
12public enum SingletonEnum {
INSTANCE_ENUM;
DataSource dataSource;
private SingletonEnum() {
dataSource = null;
}
public DataSource getDataSource() {
return dataSource;
}
}
利用枚举类实现单例模式。SingletonEnum会被编译成public final class SingletonEnum extends Enum,INSTANCE_ENUM会编译成public static final SingletonEnum INSTANCE_ENUM,根据类加载机制,初始化是线程安全的;同时,枚举序列化和反序列化禁止writeObject、readObject,不采用反射而是根据name属性和valueOf方法,防止破坏单例。