什么是单例模式
单例设计模式是一种创建型的设计模式,它保证了系统中一个类只有一个实例且一般会提供全局的访问方法.这是一种在实际开发中很常见的设计模式,主要应用在频繁创建和销毁对象的场景中,通过使用单例以提升性能.
如何实现单例模式
单例模式的实现方式有很多,其实主要是需要考量以下几点:
- 考虑避免外部通过 new 创建实例(构造器私有化);
- 考虑对象创建时的线程安全问题;
- 考虑是否支持延迟加载(lazy loading,或load on demand);
- 考虑 getInstance() 性能是否高(是否加锁);
以下是几种通过Java实现的单例模式:
饿汉式
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
饿汉式实现是在类加载的时候,instance静态实例就已经创建并且初始化好了,创建的过程是线程安全的,在实际使用时提供一个全局的方法快速返回这个实例.
不过,这种方式并不支持延迟加载,意味着提前占用内存资源.部分的资料都并不推荐这种做法:
实例占用资源多(比如占用内存多)或初始化耗时长(比如需要加载各种配置文件),提前初始化实例是一种浪费资源的行为.最好的方法应该在用到的时候再去初始化.
另一种观点是:
1)如果初始化耗时长,用它的时候才去执行这个耗时长的初始化过程会影响系统性能.将耗时的初始化操作,提前到程序启动的时候能避免在程序运行时再去初始化导致的性能问题. 2)按照 fail-fast 的设计原则,实例占用资源过多,也应该提早加载,如果有问题也能及早暴露,及早修复.
前一种观点考虑的是资源是否被有效利用,第二种则是通过系统性能以及 fail-fast 原则去考虑.在实际开发时个人觉得应该结合信息系统部署环境以及建立目的考虑,如果是一个资源紧缺型系统或者对响应没有十分高的要求,则饿汉式的单例模式不适合使用.如果不是一个资源有限的系统,并且对响应性能有一定的要求,则饿汉式也是一种可以考虑的创建方式.
懒汉式
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance(){
if (instance == null)
instance = new Singleton();
return instance;
}
}
这种方式支持延迟加载,但仅实现了单线程下的单例.多线程情况下,当一个线程正好在if (null != instance)为true判断完成时时间片用完.另一个线程同样进到这个判断,因为instance还没有实例,所以创建并返回了实例,等到第一个线程继续执行时就会再次创建新的实例. 如何解决呢?可以将getInstance()改为同步方法:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
这样就实现了在多线程并发情况下的单例,但随之也带来效率问题.每个线程在执行getInstance()时都需要进行同步,而实例化代码实际只需要执行一次,后续的调用只需要return instance就可以了.所以这是一种低效的创建方式,实际开发并不推荐使用. 那是否可以通过同步代码块缩小同步的粒度呢?比如:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
我们看到是在判断instance为空后加锁,其实并没有解决多线程下单例的问题.所以上面的实现是有问题的,那怎么修改呐?
双重检测
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
只需要在同步代码块中执行时再加一个instance为空的判断就能保证即使多个线程进入,只会有一个线程创建实例。而创建完成之后,其他线程再次调用getInstance(),并不会进入同步块中,直接return instance。 妙哉! 这种进行2次判断的方式,称为Double-Check.由懒汉式变化而来,所以支持lazy loading,加锁保证线程安全的同时,在响应性能上也很高效.推荐在开发中使用.
静态内部类
再看一种比较简洁的实现
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
又是一种优雅的实现,真·没有浪费一句话. 我们观察下代码,形似饿汉式. 但其实它又能做到延迟加载,当Singleton类被加载的时候,并不会创建SingletonHolder实例化对象,而是在首次调用getInstance()时被加载,且实例化INSTANCE.详见Java 静态内部类的加载时机.并且实例创建过程由JVM保证其线程安全性及唯一性,又不用上锁,所以性能也很好.是非常推荐的一种单例实现.
枚举
enum Singleton {
INSTANCE;
private String attribute;
/**getter,setter or any other behaviors blabla...*/
...
}
这种实现方式通过Java枚举类型本身的特性(since JDK 1.5),保证了实例创建的线程安全性和实例的唯一性,并且还能防止通过反序列化破坏单例模式. Joshua J. Bloch在Effective Java(Enforce the singleton property with a private constructor or an enum type))中提倡使用这种方式.
总结
本篇简单介绍了单例模式以及单例模式的几种Java实现.可用的实现有饿汉式,双重检测,静态内部类以及枚举.其中静态内部类和枚举是个人认为比较优雅的实现方式. 如果确定此类一定会被使用,可以选择饿汉式,枚举. 如果需要一定条件才被使用,可以选用支持懒加载的几种实现. 当然也要视具体情况而定对吧{% blur 废话 %},比如虽然我不确定会不会被使用,但实例化此类需要耗费很长的时间,然后你或者用户又是一个性能强迫症患者,那当然选择饿汉式或者枚举啦.