设计模式系列-单例模式

设计模式系列 - 单例模式

image.png

是什么?

定义: 确保一个类只有一个实例,并且提供访问该实例的静态方法。

单例的特点:

  • 在JVM中有且只能有一个实例存在。
  • 构造器必须私有(private修饰),禁止外部类通过构造器创建。
  • 提供一个全局公开的 getInstance() 方法获取该实例。

饿汉式 - 线程安全

饿汉式,简单理解就是比较饿,事先就迫不及待创建好实例, 然后调用 get 方法的时候,直接返回该实例即可。

直接创建:

carbon _1_.png

静态代码块创建:

carbon _3_.png

因为是事先创建好实例,所以没有线程安全问题。

懒汉式 - 线程不安全

懒汉式,可以理解为它懒,只有到真正要用到实例的时候,它才会去创建。

简单实现 ~~

carbon _4_.png

这种写法会有会有线程问题, 如果多个线程都执行到 if(lhSingleton == null) 并且通过,那么就会创建多个实例。

要解决这个问题也简单,给 getInstance() 方法加锁就行了,保证同一时刻只有一个线程获取实例。即可。说干就干 ~~

carbon _6_.png

这样是比较干脆的解决了线程安全问题。

这样做有个缺点,就是已经有实例了,每次调用还是要加锁排队,极大的影响性能。不推荐这样写。

我们要做到,只有最开始需要创建实例的时候,才加锁同步。那继续优化吧 ~~ 也就是双检锁了!

双检锁 - 线程安全

carbon _7_.png

  • 先检查实例是否已存在,不存在才加锁
  • 考虑到有多个线程会通过第一次的判断, 即使加了锁,这些线程依旧会排队执行同步代码块中的创建实例逻辑,还是可能会创建多次的,所以需要在同步代码块种进行二次判断。

sjsSingleton 采用 volatile 关键字修饰也是很有必要的, sjsSingleton = new SJSSingleton();这段代码其实分为三步执行:

  1. 为 sjsSingleton 实例在堆中分配内存空间
  2. 初始化 sysSingleton
  3. 将 jssSingleton 指向分配的内存地址

但是由于 JVM 具有指令重排序的特性,执行顺序可能变成1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下,会导致一个线程获得还没有初始化的实例。

使用 volatile 可以禁止 JVM 指令重排, 保证在多线程环境下也能正常运行。

volatile 除了禁止JVM 指令重排之外,还可以保证变量内存可见性,想具体了解,可以去查看相关资料!

本文由博客一文多发平台 OpenWrite 发布!

posted @ 2021-10-29 18:28  君子坐而论道  阅读(177)  评论(0编辑  收藏  举报