🌴单例模式
单例模式是校招中最常考的设计模式之一.
那么啥是设计模式呢?
- 设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.
- 软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.
那么什么是单例模式呢?
- 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
- 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例
单例模式具体的实现方式, 又分成 “饿汉” 和 “懒汉” 两种
🍀饿汉模式
饿汉模式,就是它很饿,它的对象早早的就创建好了
这种方式比较常用,但容易产生垃圾对象。
类加载的同时, 创建实例
代码实现如下:
class Singleton { //创建新实例,static修饰,只创建一次 private static Singleton instance = new Singleton(); //构造方法进行封装,不能创建新实例 private Singleton() {} //唯一实例的获取方法 public static Singleton getInstance() { return instance; } }
- 优点:没有加锁,执行效率会提高。
- 缺点:类加载时就初始化,浪费内存。
🎍懒汉模式
懒汉模式,顾名思义就是懒,没有对象需要调用它的时候不去实例化,有人来向它要对象的时候再实例化对象
🚩单线程版(线程不安全)
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。
因为没有加锁synchronized,所以严格意义上它并不算单例模式
代码实现如下:
class Singleton { private static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { //需要用到时进行创建 if (instance == null) { instance = new Singleton(); } return instance; } }
上面的懒汉模式的实现是线程不安全的.
- 线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致
创建出多个实例.
但是一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改instance 了)
🚩多线程版
所以我们对上述代码进行了优化
我们可以加上 synchronized 可以改善这里的线程安全问题.
优化代码如下:
class Singleton { private static Singleton instance = null; private Singleton() {} public synchronized static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
- 优点:第一次调用才初始化,避免内存浪费。
- 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率
该版的懒汉模式还存在一个问题,就是内存可见性的问题,不知道内存可见性的宝子可以去看看博主写的【JavaEE初阶】 volatile关键字 与 wait()方法和notify()方法详解里面对volati关键字部分的讲解
🚩多线程版(改进)
所以我们针对上述多线程版的懒汉模式进行了改进
以下代码在加锁的基础上, 做出了进一步改动:
- 使用双重 if 判定, 降低锁竞争的频率.
- 给 instance 加上了 volatile.
代码实现如下:
class Singleton { private static volatile Singleton instance = null; private Singleton() {} public static Singleton getInstance() { //需要用到时进行创建 if(instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
那么双重 if 判定 / volatile是怎么达到这样的效果的呢?
- 加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.
- 因此后续使用的时候, 不必再进行加锁了.
- 外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
- 同时为了避免 “内存可见性” 导致读取的 instance 出现偏差, 于是补充上 volatile .
- 当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,其中竞争成功的线程, 再完成创建实例的操作.
- 当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.
比如一下实例:
- 有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同一把锁
就像有三个好哥们都在追校花
- 其中线程1 率先获取到锁, 此时线程1 通过里层的 if (instance == null) 进一步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来.
其中有个哥们就对校花说:你有男朋友吗?如果没有我做你男朋友吧,这时候其他哥们是在门口等待里面情况的
- 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过里层的 if (instance == null) 来确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了
校花答应了第一个哥们后,那哥们就走了
这时候另外一个哥们又来问:能做你男票不?
校花此时就说:我已经有男朋友了
此时这个哥们就不能成为校花的男朋友了
- 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了, 从而不再尝试获取锁了. 降低了开销
那哥们出去后就和追求校花的人说,校花已经有男盆友了
这时候等待的人听见这个消息就不会再进去询问了
⭕总结
关于《【JavaEE初阶】 饿汉模式与懒汉模式详解与实现》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!