持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情
介绍
单例(Singleton)模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建模式。
单例模式是23种设计模式中常用的一种,也是最简单,最常见的。
特点
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
分类
一. 懒汉式
在调用它时,它才会创建实例,它不会随着类的加载而创建。
顾名思义,不主动干活,叫他干活,他才干。
懒汉式又分4种方式。
1. 懒汉式-方式1(线程不安全)
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
在调用getInstance方法时,才创建实例,其优点是不占用内存。在单线程下,线程安全,但在多线程时,线程不安全,所以此懒汉式为线程不安全的懒汉单列模式。
2. 懒汉式-方式2--同步(线程安全)
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
以上方式解决了线程安全问题,因为加上了synchronized关键字,效率低,只有第一次调用初始化之后,才需要同步,初始化之后都不需要进行同步。锁的粒度太大,影响了程序的执行效率。
3. 懒汉式-方式3-双重检查锁(线程安全)
public class LazySingleton {
private static volatile LazySingleton singleton;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (singleton == null) {
synchronized(LazySingleton.class){
if (singleton == null) {
singleton = new LazySingleton();
}
}
}
return singleton;
}
}
锁前进行了一次判空,锁后也进行了一次判空,顾名思义双重检查锁。其在判空后才使用了类锁,减少了synchronized关键字的使用,大大降低了锁的粒度。
这里需要注意的是在实例变量前使用了volatile,其目的是解决空指针问题,原因是JVM在实例化对象的时候会进行优化和指令重排序操作。(即其他线程可能拿到一个未初始化的对象)
volatile的重要特性:可见性
4. 懒汉式4-静态内部类(线程安全)
public class LazySingleton {
private LazySingleton() {}
private static class SingletonInner {
private static final SingletonInner INSTANCE = new SingletonInner();
}
public static LazySingleton getInstance() {
return SingletonInner.INSTANCE;
}
}
静态内部类单例模式中实例由内部类创建,由于JVM 在加载外部类的过程中, 是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
二. 饿汉式
饿汉式,即直接创建实例。加载类时就创建实例。
懒汉式分2种方式。
1. 饿汉式1-静态变量(线程安全)
public class Singleton {
private Singleton() {}
private static Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton;
}
}
静态变量单列模式在类加载时就创建了实例,避免了线程同步的问题,且没有使用锁,效率较高,但占用内存,因其随着类加载而创建实例,如果一直未使用该实例,则会造成内存浪费。
2. 饿汉式2-静态代码块
public class Singleton {
private Singleton() {}
private static Singleton singleton;
static{
singleton = new Singleton();
}
public static Singleton getInstance() {
return singleton;
}
}
其也是随着类加载而创建实例,和方式1一样会存在内存浪费的问题。
单列模式优缺点
优点:只创建了一个实例,节约系统资料,降低系统开销,提升系统性能。
缺点:只有一个实例,导致实例职责过重,违背了“单一职责”,而且没有抽象层,扩展较难。