文章内容
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,使用单例模式的类只有一个对象实例。
单例模式的五种实现方式
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会改内部生成方法,然后在初始化执行方法 —— 即执行静态变量的赋值动作。
- 虚拟机会保证 方法在多线程环境下使用加锁同步,只会执行一次 方法。
- 这种方式不仅实现延迟加载,也保障线程安全。