文章内容
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,使用单例模式的类只有一个对象实例。
单例模式的五种实现方式
1、饿汉单例 – 非多线程安全,非懒加载
饿汉需要直接创建实例。
01 02 03 04 05 06 07 08 09 10 | public class HungryHanModel { private static HungryHanModel hungryHanModel = new HungryHanModel(); private HungryHanModel() {} public static HungryHanModel getInstance(){ return hungryHanModel; } } |
- 缺点: 类加载就初始化,浪费内存
- 优点: 没有加锁,执行效率高。还是线程安全的实例
2、懒汉单例 – 非线程安全的懒汉单例,懒加载
懒汉单例:在类初始化不会创建实例,只有被调用时才会创建实例。
01 02 03 04 05 06 07 08 09 10 11 12 13 | 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 方法,只有执行完毕之后,其他线程才能进入该方法,同一时间只有一个线程才能进入该方法。
01 02 03 04 05 06 07 08 09 10 11 12 13 | public class LazyManModeSync { private static LazyManModeSync lazyManlModeSync; private LazyManModeSync() {} public static synchronized LazyManModeSync getInstance() { if (lazyManlModeSync == null ) { lazyManlModeSync = new LazyManModeSync(); } return lazyManlModeSync; } } |
这样配置虽然保证了线程的安全性,但是效率低,只有在第一次调用初始化之后,才需要同步,初始化之后都不需要进行同步。锁的粒度太大,影响了程序的执行效率。
4、双重检验懒汉单例 – 多线程安全,懒加载
使用 synchronized 声明的方法,在多个线程访问,比如第一条线程访问时,其他线程必须等待第一条线程执行完毕之后才能访问,这样会大大的降低的程序的运行效率。这个时候在加了同步锁的懒汉模式进行锁粒度的一个优化,锁住方法中的部分代码块,优化时间。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | 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类。而静态内部类是线程安全的。
01 02 03 04 05 06 07 08 09 10 11 12 | 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会改内部生成方法,然后在初始化执行方法 —— 即执行静态变量的赋值动作。
- 虚拟机会保证 方法在多线程环境下使用加锁同步,只会执行一次 方法。
- 这种方式不仅实现延迟加载,也保障线程安全。