单例模式(Singleton Pattern
)
意图
确保一个类只有一个实例,并提供一个全局访问点。
单例模式的三个要点:1.单例类只有一个实例对象;2.该单例对象必须由单例类自行创建;3.单例类对外提供一个访问该单例的全局访问点。
动机
对于系统中的某些类来说,只能有一个对象,例如:线程池、缓存、注册表的对象等。若制造出多个实例,就会导致许多问题的产生,如:程序行为异常、资源使用过度、结果不一致等。
如何保证一个对象只能被实例化一次?
利用静态变量、静态方法和适当的访问修饰符,可以实现。而更好的方法是让单例类自身负责管理它的唯一实例。
适用性
单例常常用来管理共享的资源,例如:Windows 的回收站、操作系统中的文件系统、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框等常常被设计成单例。
结构

实现
- 饿汉式单例
public class Singleton {
// 私有的静态属性
private static Singleton instance = new Singleton();
// 私有的构造方法
private Singleton() {
}
// 公共的静态方法,实例的全局访问点
public static Singleton getInstance() {
return instance;
}
}
类一旦加载就创建一个实例,保证在调用 getInstance 方法之前实例已经存在了。线程不安全问题主要是由于 instance 被实例化多次,采取直接实例化 instance 的方式就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
- 懒汉式单例(非线程安全)
public class Singleton {
//私有静态变量 instance 被延迟实例化
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
// 如果多个线程能够同时进入,将导致实例化多次 instance
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
私有静态变量 instance 被延迟实例化,他好处是,如果没有用到该类,那么就不会实例化,从而节约资源。但是这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (instance == null) ,并且此时 instance 为 null,那么会有多个线程执行 instance = new Singleton();
- 双重校验锁机制(线程安全)
public class Singleton {
// volatile修饰共享变量,保证了不同线程对变量进行操作时的可见性,可以禁止 JVM 的指令重排序
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getUniqueInstance() {
// 第一次检查,用来避免 instance 已经被实例化也去做同步操作
if (instance != null) {
// 不创建
} else {
synchronized (Singleton.class) {
// 第二次检查,加锁,确保只有一个线程进行实例化操作
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重校验锁(Double Check Locking
,简称DCL
),资源利用率高,第一次执行getInstance时单例对象才被实例化;但是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗、多余的同步和线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效。
- 静态内部类单例模式(线程安全)
public class Singleton {
// 静态内部类,只在第一次使用时进行类加载
private static class MySingletonFactory {
// 使用final修饰,可以避免变量被重新赋值,JVM也不用去跟踪该引用是否被更改
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return MySingletonFactory.INSTANCE;
}
}
静态内部类,只在第一次使用时进行类加载,也就是说当调用 getInstance() 从而触发 MySingletonFactory.INSTANCE 从而触发时 MySingletonFactory 才会被加载,此时初始化 INSTANCE 实例,且 JVM 能确保 INSTANCE 只被实例化一次。(不仅可以延迟实例化,而且JVM 提供了对线程安全的支持)。
- 序列化与反序列化实现单例模式(线程安全)
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static class MySingletonFactory {
private static final SerializeSingleton instance = new Singleton();
}
private Singleton() {}
public static SerializeSingleton getInstance() {
return MySingletonFactory.instance;
}
// 不加readResolve(),默认的方式运行得到的结果就是多例的
protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法!");
return MySingletonFactory.instance;
}
}
// 测试伪代码
public class Test{
public static void main(String[] args) throws {
Singleton singleton = Singleton.getInstance();
File file = new File("MySingleton.txt");
// 序列化
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton);
System.out.println("序列化 hashCode: "+singleton.hashCode());
// 反序列化
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton rSingleton = (Singleton) ois.readObject();
System.out.println("反序列化 hashCode: "+rSingleton.hashCode());
// 资源释放
}
}
在反序列化时,ObjectInputStream
的 readObject()
的内部代码执行顺序:readObject() --> readObject0() --> readOrdinaryObject() --> invokeReadResolve() --> readResolveMethod.invoke()
invokeReadResolve()
中 使用 readResolveMethod.invoke()
克隆对象。换句话说,invokeReadResolve()
使用反射机制创建新的对象,从而破坏了单例唯一性。
readResolve()
会在 ObjectInputStream
会检查对象的 class
是否定义了 readResolve()
。如果定义了,将由 readResolve()
指定返回的对象。返回对象的类型一定要是兼容的,否则会抛出 ClassCastException
。
- 枚举实现单例模式(线程安全)
public enum EnumSingleton implements SingletonInterface {
INSTANCE {
@Override
public void doSomething() {
System.out.println("EnumSingleton singleton");
}
};
public static EnumSingleton getInstance() {
return EnumSingleton.INSTANCE;
}
}
public interface SingletonInterface {
void doSomething();
}
已知应用
java.lang.Runtime
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
// ......
}
@Test
public void testRuntime() {
Runtime r1 = Runtime.getRuntime();
Runtime r2 = Runtime.getRuntime();
//“==”为地址判断,为true表示:r1与r2指向同一对象。
System.out.println(r1 == r2); // 输出 true
}
以上为 java.lang.Runtime
类的部分代码,是饿汉式单例模式,在该类第一次被 classloader
的时候创建唯一实例。
Runtime
类封装了 Java
运行时的环境。每一个 Java
程序实际上都是启动了一个 JVM
进程,每个 Java
程序都有一个 Runtime
类实例,使应用程序能够与其运行的环境相连接。由于Java
是单进程的,所以,在一个JVM
中,Runtime
的实例应该只有一个。
Spring
依赖注入Bean
实例(默认单例)