设计模式-单例模式

单例模式

  单例模式是比较常用的设计模式之一,单例对象的类必须保证全局只有一个实例存在。其实现方式有很多种,本篇博客主要介绍常见的几种实现方式。

  单例模式最基本的原则:保证该类构造方法的访问权限为private。

饿汉模式

  饿汉模式是指类初始化的时候先创建好该类实例,获取实例的时候总是返回该实例。

1
2
3
4
5
6
7
8
9
10
11
12

public class StarveSingleton {

//先产生实例
private static StarveSingleton instance = new StarveSingleton();
//保证构造函数私有化,这样无法从外部初始化该类
private StarveSingleton(){}
//提供获取实例方法
public static StarveSingleton getInstance() {
return instance;
}
}

懒汉模式

  懒汉模式可以延迟类的初始化,即类加载阶段不进行初始化操作,只在第一次使用单例类的时候进行初始化,主要用于一些类初始化开销比较大的情况。
  缺点:多线程情况下需要特殊处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LazySingleton {

//初始化是该引用为null
private static LazySingleton instance = null;
//私有化构造方法
private LazySingleton() {

}
//获取类实例方法
public static LazySingleton getInstance() {
//第一次调用getInstance方法时,完成初始化
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}


}

静态内部类

  饿汉模式不能实现延迟加载,占用内存;懒汉模式需要进行线程安全控制,并且影响性能。这时候静态内部类模式提供了另一种比较好的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public class StaticSingleton {

//私有化构造方法
private StaticSingleton() {

}
//静态内部类
private static class SingletonHolder {
private static final StaticSingleton STATIC_SINGLETON = new StaticSingleton();
}
//获取类实例方法
public static StaticSingleton getInstance() {
return SingletonHolder.STATIC_SINGLETON;
}

}

  由于外部类的初始化并不会引发静态内部类SingletonHolder的初始化,所以只有在第一次调用getInstance方法时才会触发静态内部类的初始化,从而完成实例化工程。

枚举类

  枚举类是目前比较推荐的实现单例的方案,因为枚举类实现简单,并且可以保证线程安全和序列化的安全。

1
2
3
4
5
6

public enum EnumSingleton {

Instance;

}

双检锁

  前面提到过懒汉模式可能会遇到线程安全问题,双检索实现方式可以保证线程安全和延迟初始化。但是该模式只能保证在1.5版本之后的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

public class DoubleCheckSignelton {
//引用设置为空,并且用volatile修饰
private static volatile DoubleCheckSignelton instance = null;
//私有化构造方法
private DoubleCheckSignelton() {

}
//提供获取实例方法
public static DoubleCheckSignelton getInstance() {
//第一次判断引用是否为空
if (instance == null) {
//防止多条线程同时实例化该类,所以加锁
synchronized (DoubleCheckSingleton.class) {
//此时有可能其他线程已经完成了实例化,所以再进行一次非空检测,确认是否需要进行类的实例化
if (instance == null) {
//确认没有进行过实例化,new一个新实例
instance = new DoubleCheckSignelton();
}
}
}
return instance;
}


}

相比枚举实现方式,双检索复杂一些。其中instance必须要用volatile修饰,此处的volatile并不是为了保证可见性,而是为了禁止重排序。产生新实例的时候一般按照开辟内存空间-创建实例-引用指向实例内存地址,但是重排序可能会将这个顺序改为开辟内存空间-引用指向实例内存地址-创建实例,如果一个线程按照第二种顺序进行操作,最后创建实例出现了问题,没有完成创建对象的工作,下一个线程进行非空检测时发现引用已经指向了新的实例地址,认为初始化工作已完成,直接返回了该引用,但实际上无法使用该实例。所以需要volatile保证类的初始化顺序。

总结

  以上就是常见的实现单例模式的方法,但是上述模式并不能完全保证全局只有一个实例,下一篇我们将深入地讲解哪些操作可以破坏单例模式,以及如何防止这些情况的发生。

-------------本文结束感谢您的阅读-------------

本文标题:设计模式-单例模式

文章作者:小建儿

发布时间:2018年07月19日 - 14:07

最后更新:2018年08月06日 - 10:08

原始链接:http://yajian.github.io/设计模式-单例模式/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。