一、简介:
单例模式(Singleton Pattern)是一种常用的软件设计模式。《设计模式》一书中对其介绍:“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
在某些时候,一个系统只有一个实例是很重要且必要的,比如:Windows系统中的任务管理器, 同时只能存在一个,因为系统瞬态是固定的,存在多个任务管理器也是在浪费资源。
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
缺点:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
二、单例模式的实现(Java):
- 这里只介绍典型的四种实现方式,懒汉、饿汉、双检锁、登记式/静态内部类。至于其他变种也都是在此基础上变化的,看多了反而凌乱。
- 实现单例注意3个点:1.单例类只能有一个实例(构造函数私有化)。2.单例类必须自己创建自己的唯一实例。3.暴露给外部一个访问点。
1.懒汉模式:
- 多个线程可能同时进入if中,最终会创建多个实例,单线程情况下可以使用
- 懒汉模式线程不安全,但是有人会想,直接在getInstance()方法加个synchronized关键字不就好了?确实,这种写法是能保证多线程安全,但是线程每次获取实例时,都需要先拿到锁,影响效率。
/**
* 延迟加载,线程不安全
*/
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
2.饿汉模式:
- 基于 ClassLoader 机制避免了多线程的同步问题,但是在类加载时就创建了实例,浪费了内存。
- java中的Runtime类就是一个典型的单例实现,它记录了java程序运行时的环境,且允许应用程序与应用程序正在运行的环境进行交互。
/**
* 无延迟加载,线程安全
*/
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
3.双检锁模式:
- 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
- volatile 关键字作用主要是防止 instance = new Singleton() 指令重排,因为这句代码不是一个原子操作,而是①new Singleton()堆中分配内存②初始化Singleton成员变量③instance指向内存中刚开辟出的空间,如果不加volatile关键字,那么执行顺序①②③,由于指令重排可能变为①③②,当执行完①③之后,如果被另一个线程抢占了资源,那么判断instance==null时,结果为false,然后会直接返回instance,此时未初始化,导致使用时可能会报错。
- -
/**
* 延迟加载,线程安全
*/
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
//第一次检测,避免每次调用getInstance()时都需要获取同步锁
//提高了效率
if(instance==null){
//this
synchronized(Singleton.class){
//第二次检测,保证多线程下安全,因为可能多个
//线程同时进入this位置。
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
4.登记式/静态内部类:
- 这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。
/**
* 延迟加载 多线程安全
*/
private Singleton(){}
private static class InnerSingleton{
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance(){
return InnerSingleton.INSTANCE;
}