单例模式解析
单例模式是设计模式中使用最多的模式之一,它是一种对象的创建模式,用于产生一个对象的实例,确保系统中一个类只有一个实例。在Java语言中,单例模式有以下好处:
- 对于频繁使用的对象,单例省略了创建对象的时间,对于重量级对象而言,极大减少了系统开销。
- 由于new的次数减少,因此减少了系统对内存的使用频次,减轻了GC压力,缩短了GC停顿时间。
因此对于系统的关键组件和频繁使用的对象,可以设计为单例模式,减少系统的开销,提高系统性能。
单例模式的参与者很少,只有单例类和使用者,关系表如下:
| 角色 | 作用 |
|---|---|
| 单例类 | 提供单例的工厂,返回单例 |
| 使用者 | 获取并使用单例 |
类图如下:

实现方式一(饿汉):
单例模式的核心在于通过一个接口返回唯一实例化对象,简单实现如下:
/**
* 单例模式--饿汉(无法做到延时加载)
*/
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
/**
* 私有构造方法
*/
private HungrySingleton(){
System.out.println("创建了对象");
}
/**
* 对外暴露的获取唯一实例的接口
* @return
*/
public static HungrySingleton getInstance(){
return instance;
}
/**
* 序列化,反序列化保证单例
* @return
*/
private Object readResolve(){
return instance;
}
}
原理:单例类必须私有化构造方法,保证不会在系统其他地方调用,其次instance成员变量和getInstance()方法必须是static修饰的。
注意:单例模式的这种实现方式非常简单,十分可靠,唯一不足是无法做到延时加载。假设单例的创建过程十分缓慢,由于instance的成员变量是由static修饰的,在JVM加载单例类的时候,单例对象就会存在,如果单例类还在系统中扮演别的角色,那么系统中任何使用单例类的地方都会初始化这个单例对象,而不管是否被用到。
实现方式二(懒汉):
为了解决上述问题,并提高系统在相关函数调用的反应速度,就需要加入延时加载机制,懒汉模式。
/**
* 单例模式--懒汉(效率低,延时加载)
*/
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
System.out.println("LazySingleton is create");
}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
return new LazySingleton();
}
return instance;
}
}
原理:首先,静态成员变量instance赋值为null,确保系统启动时没有额外负载,其次在getInstance()方法中判断当前单例instance对象是否存在,存在则返回,不存在创建单例对象。
注意:getInstance()必须是线程同步的,否则在多线程条件下,当线程1新建单例完成赋值前,线程2可能判断instance为null,线程2也创建了单例对象,导致多个实例被创建,因此同步关键字是必须的。使用上述单例模式的实现方式,虽然实现了延时加载,但是和第一种实现(饿汉)相比,引入了同步关键字,因此在多线程场景下,加载速度远远大于第一种实现方式,影响系统性能。
实现方式三(内部类):
继续改进,创建内部类:
/**
* 使用内部类来维护单例的实例,当StaticSingleton被加载时候,内部类并没有被初始化
* (instance并没有被初始化),调用getInstance()才会被初始化。
*/
public class StaticSingleton {
private StaticSingleton(){
System.out.println("StaticSingleton is create");
}
/**
* 内部类,创建单例对象
*/
private static class StaticSingleHolder{
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance(){
return StaticSingleHolder.instance;
}
}
原理:在这个实现中,使用内部类来维护单例实例,当StaticSingleton被加载的时候,内部类没有被初始化,可以确保StaticSingleton加载到JVM中,不会初始化单例类,当调用getInstance()时才会加载StaticSingleHolder,初始化instance对象,同时由于实例的建立是在类加载时完成的,对线程友好,getInstance()不需要使用同步关键字。
注意:使用内部类实现的单例,既可以实现延时加载也避免使用同步关键字,是比较完善的实现。但是如果通过反射机制强行调用私有构造方法,就会生成多个单例。同时序列化和反序列化可能破坏单例(饿汉代码readResolve()方法),场景不多见,如果存在,多加注意。
[^《Java性能程序优化 让你的Java程序更快、更稳定》 葛一鸣]