Day11 多线程进阶 8 单例模式
uwupu 啦啦啦啦啦

单例模式

创建的对象只有一个,程序运行过程中只使用这一个对象

  1. 构造方法私有private
  2. 程序运行过程中只有一个,使用getInstance()方法获取对象。

饿汉式单例

恶汉!!!

一次性分配所有空间。

1
2
3
4
5
6
7
8
9
10
11
12
//饿汉式单例
public class Hungry {
private final static Hungry HUNGRY = new Hungry();

public static Hungry getInstance(){
return HUNGRY;
}
private Hungry(){

}
}

懒汉式单例

使用时分配空间。

普通的懒汉式单例(仅用于学习)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
//懒汉式单例
public class LazyMan {
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}
}

相对于“饿汉式单例”,懒汉式单例在使用时才分配内存,避免了内存占用。

多线程下不适用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//懒汉式单例
public class LazyMan {
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}

//多线程
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
1
2
3
4
Thread-0
Thread-4
Thread-1
Thread-2

出现错误:多线程下多次创建对象。

DCL懒汉式单例(仅用于学习)

懒汉式单例dev0.2

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//懒汉式单例
public class LazyMan {
private static LazyMan lazyMan;
//双重监测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
}

}
}
return lazyMan;
}
private LazyMan(){

}
}

将getInstance()方法中创建对象的过程添加了“双重检测锁”。

解决了多线程下,对象反复创建的问题。

多线程下不适用2.0

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
//懒汉式单例
public class LazyMan {
private volatile static LazyMan lazyMan;

//双重监测锁模式的 懒但是单例 DCL懒汉式
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();// 不是一个原子性操作
/**
* 将这个操作分割:
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向这个空间
*
* 在多线程执行过程中,可能会发生“指令编排”,导致操作顺序错乱,并造成问题。
* 如:
* 线程A执行了顺序:132,执行到3结束时,
* 线程B中的getInstance()发现lazyMan不为null,直接返回,
* 在线程A的2执行前,也就是对象构造方法执行前,线程B使用了没有进行构造方法lazyMan对象,造成意外。
* 解决方法:在lazyMan前添加volatile,避免指令重排。
*/
}

}
}
return lazyMan;
}
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}

//多线程
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}

在getInstance()方法中,lazyMan = new LazyMan();不是一个原子类操作,可以再分:

  1. 分配内存空间
  2. 执行构造方法,初始化对象
  3. 把这个对象指向这个空间

在多线程执行过程中,可能会发生“指令编排”,导致操作顺序错乱,并造成问题。

如:
线程A执行了顺序:132,执行到3结束时,线程B中的getInstance()发现lazyMan不为null,直接返回,在线程A的2执行前,也就是对象构造方法执行前,线程B使用了没有进行构造方法lazyMan对象,造成意外。
解决方法:在lazyMan前添加volatile,避免指令重排。

正确的DCL懒汉式单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//懒汉式单例
public class LazyMan {
private volatile static LazyMan lazyMan;//添加volatile,防止代码重新编排。具体影响位置:getInstance()下的lazyMan = new LazyMan();

//双重监测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){ //双重检测锁模式 避免对象反复创建
if(lazyMan==null){
lazyMan = new LazyMan();
}

}
}
return lazyMan;
}
private LazyMan(){

}
}

反破解DCL懒汉式单例

1
2
3
4
5
6
7
//懒汉式单例
public enum LazyManV5 {
INSTANCE;
public static LazyManV5 getInstance(){
return INSTANCE;
}
}

这 ….这还是懒汉式单例吗?

 评论