设计模式之单例模式

283 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

介绍

单例(Singleton)模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建模式。

单例模式是23种设计模式中常用的一种,也是最简单,最常见的。

特点

单例模式有 3 个特点:

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  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一样会存在内存浪费的问题。

单列模式优缺点

优点:只创建了一个实例,节约系统资料,降低系统开销,提升系统性能。

缺点:只有一个实例,导致实例职责过重,违背了“单一职责”,而且没有抽象层,扩展较难。