# 1. 最简单的单例模式:

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
package com.example.demo.JUC.thread;

/**
* @author huapeng.zhang
* @version 1.0
* @date 2020/9/17 18:28
*/
public class SingletomDemo {

private static SingletomDemo singletomDemo = null;

private SingletomDemo() {
System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletomDemo()!");
}

public static SingletomDemo getInstance() {
if (singletomDemo == null) {
singletomDemo = new SingletomDemo();
}
return singletomDemo;
}

public static void main(String[] args) {
// 单线程
System.out.println(SingletomDemo.getInstance() == SingletomDemo.getInstance());
System.out.println(SingletomDemo.getInstance() == SingletomDemo.getInstance());
System.out.println(SingletomDemo.getInstance() == SingletomDemo.getInstance());
}
}

在单线程的情况下,打印结果如下:

image-20200917191550283

可以看到,虽然我们一共调用了六次 getInstance() , 但是只打印了一次构造方法输出内容,也就是只调用了一个构造函数,所获得的对象地址自然是一样的。

# 2. 多线程下的单例模式

我们对 main() 方法做一下改造,改造后的代码如下:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
// 改为多线程后 可能多次调用构造函数
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletomDemo.getInstance();
}, String.valueOf(i)).start();
}
}

打印结果为:

image-20200917192220270

多次执行下可以看到打印的次数是不同的。

可以对 getInstance() 方法添加 synchronized 加锁,保证只生成一个实例。

1
2
3
4
5
6
private static synchronized SingletomDemo getInstance() {
if (singletomDemo == null) {
singletomDemo = new SingletomDemo();
}
return singletomDemo;
}

再次执行程序发现只打印了一次构造方法输出函数,还有另外一种方法就是 DCL:double check locks双端检测模式 模式也可以达到同样的目的。

# 3.DCL + 单例模式

我们再次对 getInstance() 方法进行改造,代码如下:

1
2
3
4
5
6
7
8
9
10
11
// DCL模式(double check locks双端检测模式)
private static SingletomDemo getInstance() {
if (singletomDemo == null) {
synchronized (SingletomDemo.class) {
if (singletomDemo == null) {
singletomDemo = new SingletomDemo();
}
}
}
return singletomDemo;
}

当我们的单例模式写到这种程度的时候,基本可以应对 99% 的情况,但是由于 指令排序 的存在,还是有可能会出现问题。

1
2
3
memory = allocate(); //1.分配对象内存空间
instance(memory); //2.初始化对象
instance = memory; //3.设置instance指向刚分配的内存地址,此时instance!=null

步骤 2、3 不存在数据依赖,所以由于指令重排的关系,可能会出现:

1
2
3
memory = allocate(); //1.分配对象内存空间
instance = memory; //3.设置instance指向刚分配的内存地址,此时instance!=null
instance(memory); //2.初始化对象

# 4.Volatile + 单例模式

我们在声明 singletomDemo 时,加上 Volatile 关键字修饰,就可以达到完美的效果。

最终代码:

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
package com.example.demo.JUC.thread;

/**
* @author huapeng.zhang
* @version 1.0
* @date 2020/9/17 18:28
*/
public class SingletomDemo {

private static volatile SingletomDemo singletomDemo = null;

private SingletomDemo() {
System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletomDemo()!");
}

// DCL模式(double check locks双端检测模式)
private static SingletomDemo getInstance() {
if (singletomDemo == null) {
synchronized (SingletomDemo.class) {
if (singletomDemo == null) {
singletomDemo = new SingletomDemo();
}
}
}
return singletomDemo;
}

public static void main(String[] args) {
// 单线程
// System.out.println(SingletomDemo.getInstance() == SingletomDemo.getInstance());
// System.out.println(SingletomDemo.getInstance() == SingletomDemo.getInstance());
// System.out.println(SingletomDemo.getInstance() == SingletomDemo.getInstance());

// 改为多线程后 可能多次调用构造函数
// 可以在 getInstance上加SYNC解决问题
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletomDemo.getInstance();
}, String.valueOf(i)).start();
}
}

}

阅读次数

请我喝[茶]~( ̄▽ ̄)~*

有李说不清 微信支付

微信支付

有李说不清 支付宝

支付宝

有李说不清 贝宝

贝宝