单例模式
1)动机
对于软件系统的某些类,无须创建多个实例,如 Windows 系统的任务管理器,重复对象会浪费系统资源。
2)概述
1.定义
确保某个类只有一个实例,而且自行实例化,并向整个系统提供这个实例,提供全局访问的方法。
2.角色
Singleton(单例)
在单例类的内部只生成一个实例,并提供一个静态的 getInstance() 工厂方法,让客户可以访问它的唯一实例;
为防止在外部对其实例化,将其构造函数设为私有;
在单例类内部定义了一个 Singleton 类型的静态对象,作为外部共享的唯一实例。
3)案例-简单单例模式
实现 Windows 任务管理器,类名为TaskManager,包含构造函数TaskManager(),显示进程的方法displayProcesses(),显示服务的方法displayServices();
class TaskManager
{
// 私有成员变量
private static TaskManager tm = null;
// 私有构造方法
private TaskManager() {……} //初始化窗口
// 成员方法
public void displayProcesses() {……} //显示进程
public void displayServices() {……} //显示服务
// 工厂方法
public static TaskManager getInstance()
{
if (tm == null)
{
tm = new TaskManager();
}
return tm;
}
……
}
4)案例-饿汉式单例和懒汉式单例
1.饿汉式单例
定义静态变量时实例化单例类,当类被加载时,静态变量 instance 被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。
class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
2.懒汉式单例
第一次调用 getInstance() 时实例化,在类加载时并不自行实例化,为避免多个线程同时调用 getInstance(),使用 synchronized。
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
问题:
在 getInstance() 增加关键字 synchronized,以处理多个线程同时访问的问题,但是每次调用 getInstance( ) 时都进行线程锁定判断,在多线程高并发环境中,会导致系统性能大大降低。
方案:
减小锁范围+双重检查锁定(Double-Check Locking);
class LazySingleton {
// 原子性 volatile
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}
3.饿汉式单例类与懒汉式单例类对比
分类 | 类加载时机 | 资源利用 | 复杂度 |
---|---|---|---|
饿汉式单例类 | 类加载时 | 低 | 低 |
懒汉式单例类 | 第一次使用时 | 高 | 高 |
5)案例-Initialization Demand Holder (IoDH) 实现单例类
在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过 getInstance() 返回给外部使用。
class Singleton {
private Singleton() {
}
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
}
使用 IoDH,可以实现延迟加载,保证线程安全,不影响系统性能。
6)总结
1.优点
单例模式可以严格控制怎样以及何时访问。
在内存中只存在一个对象,可以节约系统资源,对于需要频繁创建和销毁的对象,单例模式可以提高系统性能。
允许可变数目的实例,使用与单例控制相似的方法来获得指定个数的对象实例。
2.缺点
因为单例模式中没有抽象层,所以单例类的扩展很困难;
单例类的职责过重;
如果实例化的共享对象长时间不被利用,会自动销毁并回收资源,下次利用时又将重新实例化,会导致共享的单例对象状态丢失。
3.适用场景
系统只需要一个实例对象,或者因资源消耗太大而只允许创建一个对象。