什么是单例模式
简述
单例模式(Singleton Pattern)是创建型设计模式中最简单且应用最为广泛的设计模式之一,单例模式属于创建型模式,提供了一种对象创建的思路。
使用单例模式时,目标类被要求确保有且仅有一个实例,对于系统任一对目标类的访问,不需要单独实例化类,只需要访问由目标类负责创建的唯一对象实例即可。
特点
- 使用单例模式的目标类仅有一个实例
- 由目标类自行创建管理唯一实例
- 目标类对外提供实例访问方法
应用场景
出现以下场景,可以考虑使用单例模式
- 某类实例需要被频繁的创建,使用后需要频繁销毁
- 某类实例化耗时长、占用资源多且使用频繁
- 某类实例频繁的与数据库或文件交互
- 某类包含全局状态字段,或在业务逻辑上仅应有单一实例
优缺点
使用单例模式的优点:
- 减少开销,因为内存中仅有一个实例,减少内存开销,又因为不需要频繁实例化类,减少了性能(时间)开销。
- 避免对某资源的多重占用,例如对某些文件执行写操作时,单例可以保证对文件同时只有一个写操作。
- 目标类对外提供唯一实例访问方法,实例可以作为共享资源的被访问。
- 在某些场景下,某类在逻辑上仅应存在单一实例,使用单例模式可以帮助实现。
单例模式的不足:
- 违背开闭原则(即对扩展开放,对修改封闭),想要对类进行扩展时,通常只能修改代码。
- 易违背单一职责原则,即某一功能全部写在一个类中,容易变得冗杂。
单例模式的七种实现方式
懒汉式单例
顾名思义,此种方式实现的单例模式是在目标类要被使用时才创建实例,这种懒加载的好处是在目标类未被实际使用时,不会有实例占据内存空间。
线程不安全方式
此种实现方式线程不安全,在目标类实例还未被创建时,如果有两个线程同时访问了getInstance(),容易产生多个实例,破坏实例的唯一性,不推荐使用。
public class Singleton {
public static Singleton instance;
// 使用私有构造方法以避免被外部实例化 确保单例
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
线程安全方式
针对前一种线程不安全的实现方式,可以使用synchronized关键字修饰方法,确保方法同时只能被一个线程访问。虽然此种方式可以保证线程安全同时实现单例,但在单例被创建后,synchronized便失去了本场景下的作用(确保实例的唯一性),且会降低性能开销
public class Singleton {
public static Singleton instance;
// 使用私有构造方法以避免被外部实例化 确保单例
private Singleton(){}
//使用synchronized保证线程安全
public synchronized static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
DCL双重检查锁定
前文提及懒汉式为保证线程安全引入synchronized导致性能开销提升,由此衍生出了双重检查锁定(Double Check Lock)的单例实现方式,使用volatile关键字保证可见性避免JVM指令重排引发的问题,同时使用synchronized保证实例创建过程唯一,既保证了懒加载的线程安全,又相较synchronized修饰方法减少了性能开销。
public class DCLSingleton {
// 注意instance必须要由volatile关键字修饰 目的是保证instance变量的可见性 避免由于指令重排破坏单例的唯一性
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if(instance == null) { //首先判断是否实例化
//使用synchronized加锁保证实例创建唯一
synchronized (DCLSingleton.class) {
if(instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}
饿汉式单例
饿汉式单例的思路是利用static关键字,使目标类在类加载阶段就创建唯一实例,保证实例在被访问前就已经被创建,确保唯一性,线程安全。
静态变量
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
静态代码块
public class Singleton {
private static Singleton instance;
private Singleton(){}
static{
instance = new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
静态内部类
在外部类被装载时,静态内部类不会跟着一起被装载,而是在静态内部类实际被使用的时候(getInstance被调用)才会装载并实例化,所以使用静态内部类实现单例模式具有懒加载的优点,同时JVM在装载类的过程中可以保证线程安全。
public class SingletonStaticClass {
private SingletonStaticClass(){}
public static final SingletonStaticClass getInstance(){
return SingletonInstance.instance;
}
private static class SingletonInstance{
private static final SingletonStaticClass instance = new SingletonStaticClass();
}
}
枚举
枚举实现单例是最安全且写法最简单的,JVM会保证枚举类不能被反射且构造器只能被调用一次,彻底解决了其他单例模式容易被反射和序列化反序列化攻击的问题(见下文),推荐使用。
枚举类单例
public enum EnumSingleton {
INSTANCE;
public void method() {
System.out.println("doSomething");
}
}
已有类改造
public class EnumSingletonEnhance {
private EnumSingletonEnhance(){}
public enum SingletonEnum {
SINGLETON_ENUM;
private EnumSingletonEnhance instance = null;
private SingletonEnum(){
instance = new EnumSingletonEnhance();
}
public EnumSingletonEnhance getinstance(){
return instance;
}
}
}