单例模式的五种实现方式

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,使用单例模式的类只有一个对象实例。

单例模式的五种实现方式

1、饿汉单例 – 非多线程安全,非懒加载

饿汉需要直接创建实例。

public class HungryHanModel {

    private static HungryHanModel hungryHanModel = new HungryHanModel();

    private HungryHanModel() {}

    public static HungryHanModel getInstance(){
        return hungryHanModel;
    }
}
  • 缺点: 类加载就初始化,浪费内存
  • 优点: 没有加锁,执行效率高。还是线程安全的实例

2、懒汉单例 – 非线程安全的懒汉单例,懒加载

懒汉单例:在类初始化不会创建实例,只有被调用时才会创建实例。

public class LazyManMode {

    private static LazyManMode lazyManMode;

    private LazyManMode() {}

    public static LazyManMode getInstance() {
        if (lazyManMode == null) {
            lazyManMode = new LazyManMode();
        }
        return lazyManMode;
    }
}

实例在调用 getInstance 才会创建实例,优点是不占内存,在单线程模式下,是安全的。但是多线程模式下,多个线程同时执行 if (lazyManMode== null) 进行判空,结果都为 true,会创建多个实例,所以上面的懒汉单例是一个线程不安全的实例。

3、加同步锁的懒汉单例 – 多线程安全,懒加载

为了解决多个线程同时执行 if (lazyManMode== null) 的问题,getInstance 方法添加同步锁,这样多条线程排队进入了 getInstance 方法,只有执行完毕之后,其他线程才能进入该方法,同一时间只有一个线程才能进入该方法。

public class LazyManModeSync {

    private static LazyManModeSync lazyManlModeSync;

    private LazyManModeSync() {}

    public static synchronized LazyManModeSync getInstance() {
        if (lazyManlModeSync == null) {
            lazyManlModeSync =new LazyManModeSync();
        }
        return lazyManlModeSync;
    }
}

这样配置虽然保证了线程的安全性,但是效率低,只有在第一次调用初始化之后,才需要同步,初始化之后都不需要进行同步。锁的粒度太大,影响了程序的执行效率。

4、双重检验懒汉单例 – 多线程安全,懒加载

使用 synchronized 声明的方法,在多个线程访问,比如第一条线程访问时,其他线程必须等待第一条线程执行完毕之后才能访问,这样会大大的降低的程序的运行效率。这个时候在加了同步锁的懒汉模式进行锁粒度的一个优化,锁住方法中的部分代码块,优化时间。

public class LazyManModeDoubleCheck {

    private static LazyManModeDoubleCheck lazyManModeDoubleCheck;

    private LazyManModeDoubleCheck() {}

    public static LazyManModeDoubleCheck getInstance() {
        if (lazyManModeDoubleCheck == null) {
            synchronized (LazyManModeDoubleCheck.class) {
                if (lazyManModeDoubleCheck == null) {
                    lazyManModeDoubleCheck = new LazyManModeDoubleCheck();
                }
            }
        }
        return lazyManModeDoubleCheck;
    }
}

双重检验首先判断实例是否为空,然后使用 synchronized (
lazyManModeDoubleCheck.class) 使用类锁,锁住整个类,执行完代码块的代码之后,新建了实例,其他代码都不走 if (lazyManModeDoubleCheck== null) 里面,在最开始的时候程序创建对象效率会低下。而 synchronized 里面再次判断是否可能同时有多个线程都执行到 synchronized (lazyManModeDoubleCheck.class) ,如果其中一条线程线程新建实例,那么其他线程 的lazyManModeDoubleCheck就不为空,就不会再创建实例了。

5、静态内部类 – 多线程安全,懒加载

外部类加载时,并不会加载内部类,也就不会执行 new CreateASingleInstance(),这属于懒加载。只有第一次调用 getInstance() 方法时才会加载 CreateASingleInstance类。而静态内部类是线程安全的。

public class StaticInnerClass {

    private StaticInnerClass() {}

    private static class CreateASingleInstance {
        private static final CreateASingleInstance INSTANCE = new CreateASingleInstance();
    }

    public static final CreateASingleInstance getInstance() {
        return CreateASingleInstance.INSTANCE;
    }
}

静态内部类为什么是线程安全?

  • 静态内部类利用了类加载机制的初始化阶段 方法,静态内部类的静态变量赋值操作,实际就是一个方法,当执行getInstance() 方法时,虚拟机才会加载 CreateASingleInstance静态内部类,
  • 然后在加载静态内部类,该内部类有静态变量,JVM会改内部生成方法,然后在初始化执行方法 —— 即执行静态变量的赋值动作。
  • 虚拟机会保证 方法在多线程环境下使用加锁同步,只会执行一次 方法。
  • 这种方式不仅实现延迟加载,也保障线程安全。

发表评论