单例模式
确保一个类只有一个实例,并提供该实例的全局访问点,。
类图
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
6种实现方式
实现方式总结
懒汉式-线程不安全
私有静态变量 instance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 instance,节约资源。
但多线程环境下是不安全的,如果多线程能够同时进入 if (instance == null)
,且 instance 为 null,那么会有多个线程执行 instance = new Singleton();
语句,导致多次实例化 instance。
public class Singleton {
private Singleton instance;
private Singleton(){
}
public static Singleton getInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
饿汉式-线程安全
线程不安全问题主要于 instance 被多次实例化,采取直接实例化 uniqueInstance 的方式线程安全了。
但直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
//对外提供获取实例的静态方法
public static Singleton getInstance() {
return instance;
}
}
懒汉式-线程安全
只要对 getInstance() 方法加锁,在一个时间点只有一个线程能够进入,就避免了多次实例化的问题。
但当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,性能上有一定的损耗。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重校验锁-线程安全
懒汉线程安全方式加锁范围较大,可以只针对实例化部分,且只有 instance 未被实例化时,才加锁。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
双重锁校验是为了避免多线程同时执行第一个 if ,而创建多个实例。
用 volatile 关键字是为了防止指令重排。instance = new Singleton();
这段代码分为三步。
- 分配内存空间
- 初始化对象
- 将 uniqueInstance 指向分配的内存地址
由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1 > 3 > 2,如果是多线程下,可能获得是一个还没有被初始化的实例。
静态内部类实现
当 Singleton 类加载时,静态内部类 SingletonHolder 没被加载进内存。当调用 getInstance()
方法触发 SingletonHolder.INSTANCE
时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。
这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举实现
最佳实践,实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。
public enum Singleton {
instance;
Singleton() {
}
}
优缺点
优点
- 提供了对唯一实例的访问控制。
- 由于在系统内存中只存在一个对象,因此可以节约资源。
- 允许可变数目的实例。可基于单例模式进行扩展,使用相似的方法来获得指定个数的对象实例。
缺点
- 由于没有抽象层,扩展有很困难。
- 职责过重,在一定程度上违背了“单一职责原则”。既充当了工厂角色,又充当了产品角色。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;比如 JVM 的自动垃圾回收的技术,如果实例化的对象长时间不被利用,会自动销毁并回收资源,下次又将重新实例化,将导致对象状态的丢失。
适用场景
在以下情况下可以使用单例模式:
- 系统只需要一个实例对象,或需要考虑资源消耗问题。
- 客户调用类的单个实例只允许使用一个公共访问点。
- 一个系统中要求一个类只有一个实例时。如果一个类可以有几个实例共存,就需要改进,成为多例模式。
应用
- java.lang.Runtime;
- GUI 中也有一些(java.awt.Toolkit#getDefaultToolkit() java.awt.Desktop#getDesktop())