hooyantsing's Blog

33_彻底玩转单例模式

字数统计: 562阅读时长: 2 min
2020/08/29

33 彻底玩转单例模式

狂神说Java JUC并发编程最新版通俗易懂

先说结论:枚举 是创建单例的不二之选。

单例模式:

  1. 饿汉式 (类一加载,就创建对象)

  2. DCL懒汉式 (当调用时才创建对象。DCL双重检测锁)

  3. 静态内部类式 (通过内部类的final来确保唯一)
    以上均会被 反射 机制破坏单例。

  4. 枚举式 (反射 newInstance方法源码写明遇到枚举将异常)

    案例

饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package single;

/**
* [饿汉式]
* 优点:线程安全
* 缺点:不能延时加载,浪费内存
*/
public class HungryMan {
private HungryMan(){}
private static HungryMan instance = new HungryMan();

public static HungryMan getInstance(){
return instance;
}
}

class HungryManTest{
// 测试
public static void main(String[] args) {
HungryMan hungryMan1 = HungryMan.getInstance();
HungryMan hungryMan2 = HungryMan.getInstance();
System.out.println(hungryMan1 == hungryMan2);
}
}

DCL懒汉式

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
package single;

/**
* [DCL懒汉式]
* DCL:双重检测锁模式
* 优点:线程安全,延时加载
*/
public class LazyMan {
private LazyMan(){}
// 为什么加入 volatile 关键字 看下面
private static volatile LazyMan instance;

public static LazyMan getInstance(){
if(instance == null){
synchronized (LazyMan.class){
if (instance == null){
/*
创建对象需要散步操作,涉及到指令重排
1. 分配内存
2. 执行构造方法,初始化对象
3. 把这个对象指向内存空间
如果123执行还正常,如果指令重排132,就会造成其他线程使用时出现异常,因为对象还没初始化好。使用 volatile 关键字禁止指令重排。
*/
instance = new LazyMan();
}
}
}
return instance;
}
}

class LazyManTest {
// 测试
public static void main(String[] args) {
LazyMan instance1 = LazyMan.getInstance();
LazyMan instance2 = LazyMan.getInstance();
System.out.println(instance1 == instance2);
}
}

静态内部类式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package single;

/**
* [静态内部类式]
* 优点:线程安全,延时加载
*/
public class StaticInner {
private StaticInner(){}
private static class InnerClass{
private static final StaticInner instance = new StaticInner();
}
public static StaticInner getInstance(){
return InnerClass.instance;
}
}

class StaticInnerTest {
// 测试
public static void main(String[] args) {
StaticInner instance1 = StaticInner.getInstance();
StaticInner instance2 = StaticInner.getInstance();
System.out.println(instance1 == instance2);
}
}

枚举式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package single;

/**
* [枚举式]
* 优点:枚举是创建单例的不二之选
* 缺点:不能延时加载
*/
public enum Enum {
INSTANCE;

public static Enum getInstance(){
return INSTANCE;
}
}

class EnumTest{
// 测试
public static void main(String[] args) {
Enum hungryMan1 = Enum.getInstance();
Enum hungryMan2 = Enum.getInstance();
System.out.println(hungryMan1 == hungryMan2);
}
}
CATALOG
  1. 1. 33 彻底玩转单例模式
    1. 1.0.1. 案例