设计模式1单例模式
模式定义:
保证一个类只有
一个示例,并且提供一个全局访问点
场景:
重量级的对象,不需要多个实例,如线程池,数据库连接池。
懒汉模式:延迟加载,只有在真正使用的时候,才开始实例化。
1)线程安全问题
2)double check 加锁优化
- 编译器(JIT),CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,
可以通过添加volatile关键字进行修饰,对于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
31package com.wzl.design.lazysingleton;
/**
* @ClassName LazySingletonTest
* @Description LazySingletonTest
* @Author wzl
* @Date 2020/11/15 17:35
*/
public class LazySingletonTest {
public static void main(String[] args) {
LazySingleton lazySingleton = LazySingleton.getInstance();
LazySingleton lazySingleton1 = LazySingleton.getInstance();
System.out.println(lazySingleton == lazySingleton1);
}
}
class LazySingleton{
private static LazySingleton instance;
private LazySingleton(){
}
public static LazySingleton getInstance() {
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}结果很明显为true,看起来一切都是这么正常。然后多线程呢?
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
45package com.wzl.design.lazysingleton;
/**
* @ClassName LazySingletonTest
* @Description LazySingletonTest
* @Author wzl
* @Date 2020/11/15 17:35
*/
public class LazySingletonTest {
public static void main(String[] args) {
new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}).start();
new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}).start();
}
}
class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
}
}让我们看下结果
发现没,多线程下这样写是有问题的。
这个要怎么解决呢
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
46package com.wzl.design.lazysingleton;
/**
* @ClassName LazySingletonTest
* @Description LazySingletonTest
* @Author wzl
* @Date 2020/11/15 17:35
*/
public class LazySingletonTest {
public static void main(String[] args) {
new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}).start();
new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}).start();
}
}
class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance() {
if (instance == null) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
}
}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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package com.wzl.design.lazysingleton;
/**
* @ClassName LazySingletonTest
* @Description LazySingletonTest
* @Author wzl
* @Date 2020/11/15 17:35
*/
public class LazySingletonTest {
public static void main(String[] args) {
new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}).start();
new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}).start();
}
}
class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
}我们只锁instance == null 然后创建对象的时候,但是带来的问题就是如果多个线程同时进入到
1
2
3
4
5if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}这个时候只需要再判断一次
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
44package com.wzl.design.lazysingleton;
/**
* @ClassName LazySingletonTest
* @Description LazySingletonTest
* @Author wzl
* @Date 2020/11/15 17:35
*/
public class LazySingletonTest {
public static void main(String[] args) {
new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}).start();
new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}).start();
}
}
class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
这么写的话,通过doublecheck解决了这个问题。
不过还是有问题的。指令进行重排序
为了避免这种情况
1 | package com.wzl.design.lazysingleton; |
饿汉模式:
类加载的初始化阶段就完成了实例的初始化。本质上就是借助于JVM类加载机制,保证实例的唯一性。(初始化的过程只会执行一次)及线程安全(JVM以同步的姓形式来完成类加载的整个过程)类加载的过程:
1. 加载二进制数据到内存中,生成对应的Class数据结构 2. 连接:a.验证 b.准备(给类的静态成员变量赋默认值)c.解析 3. 初始化:给类的静态变量赋初值
只有在真正使用对应的类时,才会触发初始化如(当前类是启动类即main函数所在的类,直接进行new操作,访问静态属性、访问静态方法,用反射访问类,初始化一个类的子类等.)
1 | class HungrySingleton{ |
静态内部类
1). 本质上是利用类的加载机制来保证线程安全
2). 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式
这种写法会有反射攻击问题
1 | // 反射攻击 |
静态内部类防止反射破坏
我们这么写
1 | class InnerClassSingleton { |
然后再运行看下结果
枚举类型
1). 天然不支持反射创建对应的实例,且有自己的反序列化机制
2). 利用类加载机制保证线程安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.wzl.design.singleton;
/**
* @ClassName EnumSingleton
* @Description EnumSingleton
* @Author wlz
* @Date 2020/11/16 15:20
*/
public enum EnumSingleton {
INSTANCE;
public void print() {
System.out.println(this.hashCode());
}
}序列化
1)可以利用指定方法来替换从反序列化流中的数据如下:
1
ANY‐ACCESS‐MODIFIER Object readResolve() throws ObjectStreamException;
1 | class InnerClassSingleton implements Serializable { |
源码中的例子
// Spring & JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry
// Tomcat
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
// 反序列化指定数据源
java.util.Currency