设计模式—单例设计模式总结

来源:互联网 发布:windows.onload 编辑:程序博客网 时间:2024/05/23 14:58

 看到一篇对单例描述很不错的文章,地址:https://www.2cto.com/kf/201704/631396.html

单例设计模式总结

单例模式简介

单例模式是应用最广泛的模式之一,在应用这个模式时,单例对象的类必须确保只有一个实例存在,避免产生多个对象消耗过多的资源。如在访问IO 和数据库时可以考虑使用单例模式。

单例模式的 UML 类图

来源于网络

Client - 高层客户端;

Singleton - 单例类;

实现单例模式的关键点

1). 构造函数私有化;

2). 通过一个静态方法或枚举返回单例类对象;

3). 确保单列类对象有且只有一个,尤其是在多线程环境下;

4). 确保单列类对象在反序列化是不会重新构建对象;

几种单列模式

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
public class Singleton
 
{
 
private static Singleton sInstance;
 
private Singleton()
 
{
 
}
 
public static synchronized Singleton getInstance()
 
{
 
if (sInstance == null)
 
{
 
sInstance = new Singleton();
 
}
 
return sInstance;
 
}
 
}

优点:只有在使用时才会被实例化,在一定程度上节约了资源;

缺点:第一次加载时需要及时实例化,每次调用 getInstance() 方法都进行同步,造成不必要的同步开销。

这种模式一般不建议使用。

2- 饿汉模式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton
 
{
 
private static Singleton sInstance = newSingleton();
 
private Singleton() {}
 
public static synchronized Singleton getInstance()
 
{
 
return sInstance;
 
}
 
}

优点:类加载时实例化,避免了线程不同步问题;

缺点:无法对singleton实例做延迟加载。

3 - Double Check Lock(DCL) 模式

?
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
public class Singleton
 
{
 
// private static Singleton sInstance = null;
 
//JDK 1.5 以后写法
 
private static volatile Singleton sInstance = null;
 
privateSingleton() {}
 
public static Singleton getInstance()
 
{
 
if (sInstance == null)
 
{
 
synchronized(Singleton.class)
 
{
 
if (sInstance == null)
 
{
 
sInstance = new Singleton();
 
}
 
}
 
}
 
return sInstance;
 
}
 
}

为什么 JDK1.5以后版本改变了写法呢?

这是因为 sInstance = new Singleton() 语句并非是一个原子操作,这句代码最终会被编译成多条汇编指令,大致做了3件事情:

(1)给 Singleton 的实例分配内存;

(2)调用 Singleton() 的构造函数,初始化成员字段;

(3)将 sInstance 对象指向分配的内存地址(此时 sInstance 就不是 null了);

由于 Java 编译器允许处理器乱起执行,以及 JDK1.5之前 JMM(Java Memory Model,即 Java 内存模型)中 Cache、寄存器到主内存会写顺序的规定,(2)和(3)的顺序是无法保证的,执行顺序可能是 1- 2 - 3也可能是 1 - 3 - 2,如果是后者,假设线程A在(3)执行完毕,(2)未执行时,此时被切换到线程B上,这时 sInstance 因为线程A已经执行过了(3),sInstance 已经是非空了,线程B直接获取 sInstance,再使用时就会出错,这种错误难以跟踪及发现,所以在 JDK1.5版本之后,调整了 JVM 具体化了 volatile 关键字,因此将 sInstance 的定义改成了

private static volatile Singleton mInstance = null;

就可以保证 sInstance 对象每次都是从主内存中读取。

优点:资源利用率高,第一次执行 getInstance() 时单例对象才会被实例化,效率高;

缺点:第一次加载时反应稍慢,也由于 Java 内存模型的原因偶尔会失败。

DCL 模式是使用最多的单例实现方式。

4 - 静态内部类模式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Singleton
 
{
 
private Singleton() {}
 
public static Singleton getInstance()
 
{
 
return SingletonHolder.sInstance;
 
}
 
privatestatic class SingletonHolder
 
{
 
privatestatic final Singleton sInstance = new Singleton();
 
}
 
}

当第一次加载 Singleton 类时并不会初始化 sInstance ,只有在第一次调用 getInstance() 方法时才会初始化 sInstance 。

优点: 确保线程安全并保证单例对象的唯一性。

推荐使用此单例模式实现方式。

5 - 枚举单例模式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum Singleton
 
{
 
INSTANCE;
 
public void log()
 
{
 
//....
 
}
 
}

优点: 写法简单,线程安全,并且在任何情况下它都是一个单列。

为什么说枚举单例是在任何情况下都是一个单列呢?那就是反序列化。

1至4的单例模式中,在反序列化情况下会出现重新创建对象的情况,反序列化时依然可以通过特殊的途径去创建一个类的实例,相当于调用该类的构造函数。上述实例中如要杜绝单例对象被反序列化时重新生成对象,必须加入如下的方法:

?
1
2
3
4
5
6
7
private Object readResolve() throws ObjectStreamException
 
{
 
return sInstance;
 
}

在 readResolve() 方法中将 sInstance 对象返回,而不是默认的重新生成一个新的对象,对应枚举却不存在这个问题,因此即使反序列化也不会重新生成一个新的实例。

总结 无论哪种方式实现的单例模式,核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,获取过程中必须保证线程安全、防止反序列化导致重新生成实例对象的问题。


原创粉丝点击