单例模式

单例模式

非单例

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
public class SingletonDemo {


private static SingletonDemo instance = null;

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

public static SingletonDemo getInstance() {

if (instance == null) {
instance = new SingletonDemo();
}
return instance;
}

public static void main(String[] args) {
for (int i = 0; i <= 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}


}

结果

非单例模式,构造方法被调用多次

1
2
3
4
2	 我是构造方法SingletonDemo()
3 我是构造方法SingletonDemo()
0 我是构造方法SingletonDemo()
1 我是构造方法SingletonDemo()

单例

volatile特点:可见性、禁止指令重排、不能保证原子性

synchronized特点:原子性、可见性、不能保证禁止指令重排

原子性:是指一个操作是不可中断的,要全部执行完成,要不就都不执行。线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。

可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。

禁止指令重排:程序执行的顺序按照代码的先后顺序执行。除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load->add->save 有可能被优化成load->save->add 。这就是可能存在指令重排问题。

synchronized修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。

synchronized修饰的代码,在开始执行时会加锁,执行完成后会进行解锁。而为了保证可见性,有一条规则是这样的:对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值。

synchronized修饰的代码是无法禁止指令重排和处理器优化的。如果在本线程内观察,所有操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无序的。由于synchronized修饰的代码,同一时间只能被同一线程访问。那么也就是单线程执行的。所以,可以保证禁止指令重排,但是本身不具备禁止指令重排,有点绕口。

版本一

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
public class SingletonDemo {

//加上volatile,禁止指令重排
private static volatile SingletonDemo instance = null;

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

public static synchronized SingletonDemo getInstance() {

if (instance == null) {
instance = new SingletonDemo();
}
return instance;
}

public static void main(String[] args) {
for (int i = 0; i <= 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}


}

结果

单例模式,构造方法只被调用一次

1
0	 我是构造方法SingletonDemo()

版本二

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
public class SingletonDemo {

//加上volatile,禁止指令重排
private static volatile SingletonDemo instance = null;

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

public static SingletonDemo getInstance() {

if (instance == null) {
//双端检锁机制,加锁前后进行判断
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}

public static void main(String[] args) {
for (int i = 0; i <= 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}

结果

单例模式下,构造方法只会被调用一次

1
1	 我是构造方法SingletonDemo()

参考

再有人问你synchronized是什么,就把这篇文章发给他。-HollisChuang’s Blog