设计模式系列 - 单例模式
是什么?
定义: 确保一个类只有一个实例,并且提供访问该实例的静态方法。
单例的特点:
- 在JVM中有且只能有一个实例存在。
- 构造器必须私有(
private修饰
),禁止外部类通过构造器创建。 - 提供一个全局公开的
getInstance()
方法获取该实例。
饿汉式 - 线程安全
饿汉式,简单理解就是比较饿,事先就迫不及待创建好实例, 然后调用 get 方法的时候,直接返回该实例即可。
直接创建:
静态代码块创建:
因为是事先创建好实例,所以没有线程安全问题。
懒汉式 - 线程不安全
懒汉式,可以理解为它懒,只有到真正要用到实例的时候,它才会去创建。
简单实现 ~~
这种写法会有会有线程问题
, 如果多个线程都执行到 if(lhSingleton == null)
并且通过,那么就会创建多个实例。
要解决这个问题也简单,给 getInstance()
方法加锁就行了,保证同一时刻只有一个线程获取实例。即可。说干就干 ~~
这样是比较干脆的解决了线程安全问题。
这样做有个缺点,就是已经有实例了,每次调用还是要加锁排队,极大的影响性能。不推荐这样写。
我们要做到,只有最开始需要创建实例的时候,才加锁同步。那继续优化吧 ~~ 也就是双检锁了!
双检锁 - 线程安全
- 先检查实例是否已存在,不存在才加锁
- 考虑到有多个线程会通过第一次的判断, 即使加了锁,这些线程依旧会排队执行同步代码块中的创建实例逻辑,还是可能会创建多次的,所以需要在同步代码块种进行二次判断。
sjsSingleton 采用 volatile 关键字修饰也是很有必要的, sjsSingleton = new SJSSingleton();
这段代码其实分为三步执行:
- 为 sjsSingleton 实例在堆中分配内存空间
- 初始化 sysSingleton
- 将 jssSingleton 指向分配的内存地址
但是由于 JVM 具有指令重排序的特性,执行顺序可能变成1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下,会导致一个线程获得还没有初始化的实例。
使用 volatile 可以禁止 JVM 指令重排, 保证在多线程环境下也能正常运行。
volatile 除了禁止JVM 指令重排之外,还可以保证变量内存可见性,想具体了解,可以去查看相关资料!
本文由博客一文多发平台 OpenWrite 发布!