设计模式(二)——单例模式

单例模式

​ 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。构造器私有化,不能被new出来。

项目代码:Github

单例的应用

优点

  1. 在内存中只有一个实例, 减少了内存开支
  2. 当一个对象的产生需要 比较多的资源时, 如读取配置、 产生其他依赖对象时, 则可以通过在应用启动时直接产生一 个单例对象, 然后用永久驻留内存的方式来解决
  3. 单例模式可以避免对资源的多重占用, 例如一个写文件动作, 由于只有一个实例存在 内存中, 避免对同一个资源文件的同时写操作。
  4. 单例模式可以避免对资源的多重占用, 例如一个写文件动作, 由于只有一个实例存在 内存中, 避免对同一个资源文件的同时写操作。

缺点

  1. 单例模式一般没有接口, 扩展很困难;
  2. 单例模式对测试是不利的。 在并行开发环境中, 如果单例模式没有完成, 是不能进行 测试的, 没有接口也不能使用mock的方式虚拟一个对象。
  3. 单例模式与单一职责原则有冲突。

几种单例实现:

1、饿汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SingleDemo {
private static SingleDemo instance = new SingleDemo();
//私有化构造器
private SingleDemo() {
//防止其他通过反射调用构造方法,破解单例
if (instance != null) {
throw new RuntimeException();
}
}
//对外提供统一的访问点
public static SingleDemo getInstance() {
return instance;
}
}

优点:

  • 实例的初始化由JVM装载类的时候进行,保证了线程的安全性
  • 实现简单方便,访问效率高

缺点:

  • 不能实现懒加载,资源的利用率不高

2、懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SingleDemo2 {
// 此处并不初始化实例
private static SingleDemo2 instance;

private SingleDemo2() {
if (instance != null) {
throw new RuntimeException();
}
}
/**
* 当调用此方法的时候才初始化实例, 为了实现线程安全,需要使用同步方法
*/
public static synchronized SingleDemo2 getInstance() {
if (instance == null) {
instance = new SingleDemo2();
}
return instance;
}
}

优点:

  • 只有使用这个类的时候才初始化实例,优化了资源利用率

缺点:

  • 为了实现线程安全,使用了同步方法获取,增加了访问的开销

3、双重检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SingleDemo3 {
private static SingleDemo3 instance;

private SingleDemo3() {
if (instance != null) {
throw new RuntimeException();
}
}

public static SingleDemo3 getInstance() {
//第一重检查,提高效率
if (instance == null) {
synchronized (SingleDemo3.class) {
//第二重检查保证线程安全
if (instance == null) {
instance = new SingleDemo3();
}
}
}
return instance;
}
}

优点:

  • 实现懒加载
  • 通过缩小同步区域和第一次检查提高访问效率

缺点:

  • 为了实现线程安全,使用了同步方法获取,增加了访问的开销

4、静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SingleDemo4 {
private static SingleDemo4 instance;

private static class SingleDemo4Holder {
private static final SingleDemo4 instance = new SingleDemo4();
}

private SingleDemo4() {
if (instance != null) {
throw new RuntimeException();
}
}

/**
* 调用这个方法的时候,JVM才加载静态内部类,才初始化静态内部类的类变量。由于由JVM初始化,保证了线程安全性,
* 同时又实现了懒加载
*/
public static SingleDemo4 getInstance() {
return SingleDemo4Holder.instance;
}
}

优点:

  • 即实现了线程安全,又实现了懒加载

缺点:

  • 实现稍显复杂

5、枚举类

1
2
3
4
5
6
public enum SingleDemo5 {
INSTANCE;
public void someMethod(){

}
}

优点:

  • 实现简单
  • 线程安全

缺点:

  • 不能实现懒加载

结论

如果需要懒加载就使用静态内部类方式,如果不需要就使用枚举方式

单例模式的扩展

如果要求一个类只能生产固定数量的实例。

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 SingleDemo6{
// 最多可以生成的单例数量
private static int maxNumberSingleDemo = 2;
// 定义列表存放实例
private static List<SingleDemo6> singleDemoList = new ArrayList<>();

//生成对象
static{
for(int i=0; i<maxNumberSingleDemo; i++){
singleDemoList.add(new SingleDemo6());
}
}

private SingleDemo6(){}

public static SingleDemo6 getInstance(){
Random random = new Random();
//随机调用一个实例
int number = random.nextInt(maxNumberSingleDemo);
return singleDemoList.get(number);
}

}

这种需要产生固定数量对象的模式就叫做有上限的多例模式, 它是单例模式的一种扩展, 采用有上限的多例模式, 我们可以在设计时决定在内存中有多少个实例, 方便系统进行 扩展, 修正单例可能存在的性能问题, 提供系统的响应速度。 例如读取文件, 我们可以在系 统启动时完成初始化工作, 在内存中启动固定数量的reader实例, 然后在需要读取文件时就 可以快速响应。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
显示 Gitment 评论