【JavaEE】 饿汉模式与懒汉模式详解与实现

文章目录

  • 单例模式
  • 饿汉模式
  • 懒汉模式
    • 单线程版(线程不安全)
    • 多线程版
    • 多线程版(改进)
  • ⭕总结

单例模式

单例模式是校招中最常考的设计模式之一.

那么啥是设计模式呢?

  • 设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.

  • 软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

那么什么是单例模式呢?

  • 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  • 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  • 单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

注意:

  1. 单例类只能有一个实例。

  2. 单例类必须自己创建自己的唯一实例。

  3. 单例类必须给所有其他对象提供这一实例

单例模式具体的实现方式, 又分成 “饿汉” 和 “懒汉” 两种

饿汉模式

饿汉模式,就是它很饿,它的对象早早的就创建好了

这种方式比较常用,但容易产生垃圾对象。

类加载的同时, 创建实例

代码实现如下:

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 挡住了. 也就不会继续创建其他实例.

比如一下实例:

  1. 有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同一把锁

就像有三个好哥们都在追校花【JavaEE】 饿汉模式与懒汉模式详解与实现_第1张图片

  1. 其中线程1 率先获取到锁, 此时线程1 通过里层的 if (instance == null) 进一步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来.

其中有个哥们就对校花说:你有男朋友吗?如果没有我做你男朋友吧,这时候其他哥们是在门口等待里面情况的
【JavaEE】 饿汉模式与懒汉模式详解与实现_第2张图片

  1. 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过里层的 if (instance == null) 来确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了

校花答应了第一个哥们后,那哥们就走了
这时候另外一个哥们又来问:能做你男票不?
校花此时就说:我已经有男朋友了
此时这个哥们就不能成为校花的男朋友了
【JavaEE】 饿汉模式与懒汉模式详解与实现_第3张图片

  1. 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了, 从而不再尝试获取锁了. 降低了开销

那哥们出去后就和追求校花的人说,校花已经有男盆友了
这时候等待的人听见这个消息就不会再进去询问了
【JavaEE】 饿汉模式与懒汉模式详解与实现_第4张图片

⭕总结

关于《【JavaEE】 饿汉模式与懒汉模式详解与实现》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

你可能感兴趣的:(JavaEE初阶,java-ee,单例模式,java,多线程,安全,计算机操作系统)