DesignPattern - 单例模式【建立型】

欢迎关注微信公众号:FSA全栈行动 👋java

1、单例模式介绍

经过单例模式能够保证系统中,应用该模式的类只有一个对象实例。安全

  • 好处:微信

    • 节省内存(不必存在 多个功能相同的对象)
    • 提升性能(有些对象的建立很耗系统资源)
  • 分类:markdown

    • 饿汉:提早建立对象(class 一加载就建立)
    • 懒汉:延迟建立对象(调用 getInstance() 时再建立)
  • 实现步骤:多线程

    • 私有化构造函数
    • 提供获取单例的方法

2、单例模式代码实现

一、饿汉式

  • 饿汉方式:提早建立好对象
  • 优势:实现简单,没有多线程同步问题
  • 缺点:类一加载就会建立,无论有没有使用,instance 对象一直占着内存
/** * 单式模式:饿汉式 * * @author GitLqr */
public class SingletonHungry {

	private static SingletonHungry instance = new SingletonHungry();

	private SingletonHungry() {
	}

	public static SingletonHungry getInstanceHungry() {
		return instance;
	}
}
复制代码

二、懒汉式

  • 懒汉方式:延迟建立对象
  • 优势:须要用到时才会建立对象,规避没必要要的内存浪费
  • 缺点:可能须要考虑有多线程同步问题

1)DCL (+ volatile) 方式

DCL,即双重检查锁定 (Double-Checked-Locking)函数

  • synchronized 前第一次判空:避免没必要要的加锁同步,提升性能
  • synchronized 后第二次判空:避免出现多线程安全问题
  • volatile 修饰 instance:避免指令重排序问题
/** * 单例模式:懒汉式 (DCL + volatile) * * @author GitLqr */
public class SingletonLazy {

	private static volatile SingletonLazy instance;

	private SingletonLazy() {
	}

	public static SingletonLazy getInstance() {
		if (instance == null) {
			synchronized (SingletonLazy.class) {
				if (instance == null) {
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}
复制代码

2)静态内部类 方式

  • Holder 静态内部类:外部类加载时,并不会直接致使静态内部类被加载,在调用 getInstance() 时才会触发该静态内部类被加载,因此,能够延时执行。
  • Holder.instance static 字段:同一个加载器下,一个类型只会初始化一次,故自然的线程安全。
/** * 单例模式:懒汉式 (静态内部类) * * @author GitLqr */
public class SingletonLazyClass {

	private static class Holder {
		private static SingletonLazyClass instance = new SingletonLazyClass();
	}

	public static SingletonLazyClass getInstance() {
		return Holder.instance;
	}

	private SingletonLazyClass() {
	}
}
复制代码

注意:静态内部类 相比 DCL 代码简洁不少,即有饿汉式的优点,又能够作到延时初始化,看似很完美,但其有一个致命问题,即没法传参,因此,实际开发中,要根据实际状况来选择其中一种实现方式。oop

3)枚举 方式

  • 枚举没法 new,也就无须私有化构造函数,相比 静态内部类 方式要简洁的多
  • 枚举实例建立是线程安全的
/** * 单例模式:懒汉式 (枚举) * * @author GitLqr */
public enum SingletonEnum {
	INSTANCE;

    // 枚举与普通类同样,能够拥有字段和方法
	public void method() {
		// TODO
	}
}
复制代码

注意:缺点跟 静态内部类 方式同样,外部没法传参。性能

3、DCL 单例模式的演进

阶段一:简单的对象判空

  • 缺点:线程不安全,多线程下存在安全问题
  • 解决办法:加锁
public class SingletonLazy {

	private static SingletonLazy instance;

	private SingletonLazy() {
	}

	public static SingletonLazy getInstance() {
		if (instance == null) {
			instance = new SingletonLazy();
		}
		return instance;
	}
}
复制代码

阶段二:经过加锁 synchronized 保证单例

  • 缺点:采用 synchronized 对方法加锁有很大的性能开销
  • 解决办法:锁粒度不要这么大
public class SingletonLazy {

	private static SingletonLazy instance;

	private SingletonLazy() {
	}

	public static synchronized SingletonLazy getInstance() {
		if (instance == null) {
			instance = new SingletonLazy();
		}
		return instance;
	}
}
复制代码

阶段三:DCL 双重检查锁定 (Double-Checked-Locking)

  • 优势:在多线程状况下保持高性能
  • 缺点:不安全,instance = new SingletonLazy(); 不是原子性操做,这行代码为以下 3 步:
    1. 分配内存空间
    2. 在空间内建立对象
    3. 对对象赋值给引用
  • 缘由:由于 JVM 指令重排序问题,可能致使线程中会按 1->3->2 的顺序执行(JVM 认为 2 和 3 没有前后顺序),其中步骤 3 会把 线程副内存中 的值写入主内存,其余线程就会读取到 instance 最新的值,但由于步骤 2 尚未执行,此时这个 instance 是不彻底的对象,这时其余线程中使用这个不彻底的 instance 就会出错。
  • 解决办法:使用 volatile 关键字
public class SingletonLazy {

	private static SingletonLazy instance;

	private SingletonLazy() {
	}

	public static SingletonLazy getInstance() {
		if (instance == null) {
			synchronized (SingletonLazy.class) {
				if (instance == null) {
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}
复制代码

阶段四:DCL + volatile

  • volatile:是 java 提供的关键字,能够禁止指令重排序
public class SingletonLazy {

	private static volatile SingletonLazy instance;

	private SingletonLazy() {
	}

	public static SingletonLazy getInstance() {
		if (instance == null) {
			synchronized (SingletonLazy.class) {
				if (instance == null) {
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}
复制代码

若是文章对您有所帮助, 请不吝点击关注一下个人微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不只有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~spa

相关文章
相关标签/搜索