创建型模式:关注对象的创建过程,它描述如何将对象的创建、使用分离,让用户无需关心对象的创建细节,从而降低系统的耦合度,让设计方案易于修改、扩展。
Windows 中的任务管理器,就是典型的单例模式,无论如何,只允许一个任务管理器存在:
一、概述
单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
三个要点:
- 一个类只有一个实例
- 必须自行创建这个实例
- 必须自行向系统提供这个实例
二、创建方式
① 饿汉式
饿汉式(Eager Singleton)是实现起来最简单的单例类。由于在定义静态变量的时候实例化单例类,因此在类加载时单例对象就已创建。
当类加载时,静态变量 INSTANCE 被初始化,唯一的实例被创建。
public class EagerSingleton {
//final修饰的静态引用,直接创建对象,一般不存在线程安全问题
private static final EagerSingleton INSTANCE = new EagerSingleton();
//私有的构造方法
private EagerSingleton(){ }
//静态的获取方法
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
② 懒汉式
懒汉式单例类(Lazy Singleton)的构造函数也是私有的,与饿汉式不同,懒汉式单例类在第一次被使用时将自己实例化。
从图中可以看出,懒汉式单例在类加载时并不实例化,而是在第一次调用 getInstance() 方法时实例化,这种技术被称为 延迟加载 (Lazy Load) 技术,即需要的时候再加载实例.
为了避免多个线程同时调用 getInstance() 方法,可使用 双重检查锁定 (Double-Check Locking),这种方法相比于直接使用 synchronized 关键字修饰的好处在于:
避免 一瞬间两线程同时调用getInstance() 方法时,均能通过 "instance == null"的判断,串行创建两对象的情况。这种情况产生多个单例对象,违背了单例模式的设计思想。
public 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;
}
}
如若使用双重检查锁定,需要使用volatile
修饰静态变量成员,且须在 JDK 1.5+版本运行。由于volatile
关键字会屏蔽 JVM 所做的一些代码优化,可能导致效率降低。
③ 静态内部类式
饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制繁琐,性能受影响。
为克服以上问题,可通过Initialization on Demand Holder (IoDH)
技术实现单例问题,这种方法须增加一个静态(static)内部类,再通过 getInstance() 方法返回给 外部使用,实现代码:
public class Singleton {
//私有的构造方法
private Singleton(){ }
//静态内部类
private static class HolderClass{
private final static Singleton INSTANCE = new Singleton();
}
//静态的获取方法
public static Singleton getInstance(){
return HolderClass.INSTANCE;
}
}
第一次调用getInstance() 时将加载内部类 HolderClass,在内部类定义了static 类型的变量 instance,由 JVM 来保证其线程安全性,确保该成员变量只能初始化一次。由于 getInstance() 方法没有任何线程锁定,因此对性能不会造成任何影响。
④ 枚举式(饿汉式)
单元素的 枚举类型 已经成为实现Singleton的最佳方法
—— 出自 《Effective Java》
public enum EnumSingleton {
//直接写即可,由于可直接获取对象,getInstance() 方法无直接意义
INSTANCE
}
三、特点
以上代码可能还存在反射攻击或者反序列化攻击
享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
—— 出自 《Effective Java》
☯ 优点
- 提供了唯一实例的受控访问。
- 在系统内存中只存在一个对象,可节约系统资源。
- 允许可变的数目的实例。基于单例模式可扩展获得指定数量的实例,这样的类称为多例类。
(解决了由于单例对象共享过多有损性能的问题)
☯ 缺点
- 单例模式没有抽象层,扩展较为困难。
- 单例类职责过重,一定程度上违背了单一职责原则。单例类即提供了业务方法,由提供了工厂方法,功能耦合。
- 有一定概率会被GC回收,可能存在状态丢失问题。
四、扩展:多例模式
多例模式(Multiton Pattern)是单例模式的一种扩展,有限的多例模式须配合容器,并为每个对象分配唯一 标识序号,多例模式的一种实现代码如下:
public class Multipleton {
//实例最大数量
private final static int MAX_INSTANCE = 10;
// 存放N个实例对象的容器
private static ArrayList<Multipleton> list = new ArrayList<Multipleton>(MAX_INSTANCE);
// 每个对象的序号 标识
private int no;
// 私有构造方法 防止外界应用程序实例化
private Multipleton(int no) {
this.no = no;
}
// 实例化N个对象实例
static {
// 添加Multipleton对象实例
for (int i = 0; i < MAX_INSTANCE; i++) {
list.add(new Multipleton(i));
}
}
/**
* 随机获得 实例对象
*/
public static Multipleton getRandomInstance() {
// 获得随机数字
int num = (int) (Math.random() * MAX_INSTANCE);
// 获得list中的对象实例
return list.get(num);
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
}