lusiqi

设计模式-单例模式,介绍设计模式的几种实现方式及优缺点。


简介

确保一个类只有一个实例对象,并提供该实例给全局访问。

代码实现

懒汉式线程不安全

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
/**
* @description 创建型设计模式-单例模式-懒汉式不安全
* 优点:静态变量instance被延迟实例化,没有用到该类,那么就不会被实例化,从而节约资源。
* 缺点:在多线程的环境下是不安全的,如果多个线程同时进入,会实例化多个对象。
* @author dxy
* @date 2019-12-27
*/
public class LazyUnsafeSingleton extends Singleton{

private static Singleton instance;

private LazyUnsafeSingleton() {
}

/**
* 延迟获得实例
* @return
*/
public static Singleton getInstance() {

if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点

静态变量instance被延迟实例化,没有用到该类,那么就不会被实例化,从而节约资源。

缺点

在多线程的环境下是不安全的,如果多个线程同时进入,会实例化多个对象。

饿汉式线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @description 创建型设计模式-单例模式-饿汉式安全
* 优点:解决了线程安全问题,多线程访问也会获得同一个实例对象
* 缺点:直接实例化丢失了延迟实例化带来的节约资源的好处。
* @author dxy
* @date 2019-12-27
*/
public class HungrySafeSingleton extends Singleton{

private static Singleton instance = new Singleton();

private HungrySafeSingleton() {
}

}
优点

解决了线程安全问题,多线程访问也会获得同一个实例对象

缺点

直接实例化丢失了延迟实例化带来的节约资源的好处。

懒汉式线程安全

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

/**
* @description 创建型设计模式-单例模式-懒汉式安全
* 优点:对getInstance()方法加锁,那么同一时间就只能有一个线程能够进入该方法,从而避免了多次实例化。
* 缺点:多线程情况下,当一个线程进入该方法后,其他试图进入该方法的线程都必须等待,即使instance已经被实例化了。这会让线程阻塞时间过长出现性能问题。不推荐使用。
* @author dxy
* @date 2019-12-27
*/
public class LazySafeSingleton extends Singleton{

private static Singleton instance;

private LazySafeSingleton() {
}

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点

对getInstance()方法加锁,那么同一时间就只能有一个线程能够进入该方法,从而避免了多次实例化。

缺点

多线程情况下,当一个线程进入该方法后,其他试图进入该方法的线程都必须等待,即使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

/**
* @description 创建型设计模式-单例模式-双重校验锁安全
* 优点:既延迟加载实例,又保证了线程安全
* 缺点:实现较复杂
* @author dxy
* @date 2019-12-27
*/
public class DoubleCheckSafeSingleton extends Singleton{

private volatile static Singleton instance;

private DoubleCheckSafeSingleton() {
}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点

如果只有一个if,在instance==null的情况下,如果两个线程同时执行了if语句,那么两个线程都会进入if的语句块中,虽然if块中有加锁操作,但是两个线程都会执行instance = new Singleton();只是先后执行的问题,那么就会实例化俩次。使用双重校验锁就解决了这种问题。

同时volatile 也是必要的,instance = new Singleton();这段代码其实是分三步执行的:
1.为instance分配内存空间
2.初始化instance
3.将instance指向分配的内存地址
由于JVM具有指令重排的特性,在多线程环境下执行顺序可能会变成1 3 2,使用volatile会禁止指令重排,保证了线程安全

缺点

保证线程安全的同时,不可避免的影响性能。

静态内部类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @description 创建型设计模式-单例模式-静态内部类
* @author dxy
* @date 2019-12-27
*/
public class StaticInnerClassSingleton extends Singleton{

private StaticInnerClassSingleton() {
}

private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}

public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
优点

当Singleton类被加载时,静态内部类SingletonHolder没有被加载到内存,只有当调用getUniqueInstance方法时才会触发静态内部类的初始化,JVM确保只被实例化了一次。这种方式不仅具有延迟初始化的好处,并且由JVM提供了对线程的支持

枚举实现

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
/**
* @description 创建型设计模式-单例模式-枚举实现
* 优点:反序列化创建对象
* @author dxy
* @date 2019-12-27
*/
public enum EnumSingleton {

INSTANCE;

private String objName;


public String getObjName() {
return objName;
}


public void setObjName(String objName) {
this.objName = objName;
}


public static void main(String[] args) {

// 单例测试
EnumSingleton firstSingleton = EnumSingleton.INSTANCE;
firstSingleton.setObjName("firstName");
System.out.println(firstSingleton.getObjName());
EnumSingleton secondSingleton = EnumSingleton.INSTANCE;
secondSingleton.setObjName("secondName");
System.out.println(firstSingleton.getObjName());
System.out.println(secondSingleton.getObjName());

// 反射获取实例测试
try {
EnumSingleton[] enumConstants = EnumSingleton.class.getEnumConstants();
for (EnumSingleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
firstName
secondName
secondName
secondName
优点

这种实现可以防止反射攻击,在其他实现中,通过setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。

该实现在多次序列化和序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。

经验之谈

一般情况下,不建议使用第 1 种和第 3种懒汉方式,建议使用第 2 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种静态内部类的方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

 上一篇

 评论