Java Web 实战 07 - 多线程基础之单例模式

大家好 , 这篇文章给大家带来的是单例模式 , 单例模式中分为懒汉模式和饿汉模式 , 懒汉模式是需要用的到的时候才去创建实例 , 而饿汉模式是程序一启动就立刻创建实例 , 在这其中还有很多其他问题需要我们去研究
推荐大家跳转到这里 , 观看效果更加
上一篇文章的链接我也贴在这里了
在这里插入图片描述

单例模式

单例模式是一种非常常见的设计模式

设计模式就相当于棋谱 , 前人总结出的一些固定套路 , 如果我们按照这些固定套路 , 代码就不会写的太离谱

设计模式虽然很有用 , 但是需要大家有一定的编程经验才好理解一点
未来肯定会经常和设计模式打交道 , 但是现在我们还是学习一下面试中比较常见的设计模式
校招中 , 关于设计模式 , 主要考察两个 : 单例模式、工厂模式

单例模式的目的就是 : 有些对象 , 在一个程序中应该只有一个实例 , 就可以使用单例模式
在单例模式下 , 对象的实例化就被限制了 , 只能创建一个 , 多的就不能再创建了

单例模式就是借助相关语法强制限制咱们不能创建多个实例

开车为啥要系安全带 ?
大家都不想出现事故 , 但是通过司机并不能完全保证安全
所以就出现了安全带 , 一定程度上保证了安全

在 Java 中 , 单例模式有很多种实现方式
我们主要介绍两个 : 饿汉模式 和 懒汉模式
他们俩描述的是 创建实例的时机
饿汉模式 : 程序启动立即创建实例
懒汉模式 : 程序启动先不创建实例 , 等到真正有用的时候再创建实例

大家不要把懒理解成贬义词 , 在计算机中 , 懒这个字往往意味着性能比较高
许多科技的发明都是因为人类懒 , 想要方便生活

计算机中 , 这种思想很常见
硬盘里面有许多大文件
使用文件资源管理器打开它

  1. 饿汉模式 : 把这些大文件通通读到内存中 , 读取完毕之后再让用户进行查看和修改
  2. 懒汉模式 : 只读取出当前页面能显示出的范围的文件数目 , 随着用户翻页 , 继续再读后面内容 , 用户还没翻到 , 就不着急度

或者刷抖音 , 不是一下子加载出所有的视频 , 而是一个一个加载 , 刷完了再加载下一条
或者微信朋友圈 , 只有当你点进去的时候 , 才会加载原图 , 否则就不加载

1. 饿汉模式 : 程序启动立即创建实例

// 单例模式的实现
// 饿汉模式
class Singleton {
    // 创建实例,把创建的实例赋值给一个静态变量
    // 静态变量又叫做类变量,类对象在整个程序中只有唯一一个实例->保证了单例
    private static Singleton instance = new Singleton();

    // 通过这个静态方法返回独苗实例
    // 后续如果需要使用这个实例,统一基于getInstance方法来获取咱们的独苗实例,就不需要去new了
    public static Singleton getInstance() {
        return instance;
    }

    // 构造方法设为私有:其他的类想来new就不行了
    // 从根本上禁止了其他代码来new Singleton 实例了
    private Singleton() {}
}

public class Demo19 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        
        // 创建的是同一个实例
        System.out.println(instance1 == instance2);// true
    }
}

如果我们再手动创建一个实例 , 就会报错
image.png

单例模式的关键是 : 使用静态成员表示实例 (唯一性) + 让构造方法为私有 (堵住了 new 创建新实例的方式)

那么这段代码为什么说是饿汉模式呢 ?
image.png

2. 懒汉模式 : 程序启动先不创建实例 , 等到真正有用的时候再创建实例

// 单例模式的实现
// 懒汉模式
class SingletonLazy {
    // 先不着急实例化
    private static SingletonLazy instance = null;
    
    public static SingletonLazy getInstance() {
        // 进行判断
        // 为空->创建实例
        if(instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
    
    // 构造方法同样也是 private 的
    private SingletonLazy() {}
}

public class Demo19 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

    }
}

image.png

3. 正题 : 上述的两个单例模式的代码在多线程环境下调用 getInstance() 是线程安全的吗?

饿汉模式是天然线程安全的
image.png
懒汉模式是线程不安全的
image.png
懒汉模式一旦实例创建好之后 , 后续 if 条件就进不去了 , 此时也就全是读操作了 , 也就线程安全了
所以我们要是想让懒汉模式变成线程安全的 , 就一定要解决第一次创建实例的过程

那如何解决懒汉模式的线程不安全问题呢 ?
解决办法就是加锁

// 单例模式的实现
// 懒汉模式
class SingletonLazy {
    // 先不着急实例化
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        // 进行判断
        // 为空->创建实例
        synchronized (SingletonLazy.class) {
            if(instance == null) {
                instance = new SingletonLazy();
            }
        }
        return instance;
    }

    // 构造方法同样也是 private 的
    private SingletonLazy() {}
}

public class Demo19 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

    }
}

image.png
但是目前还是有问题
懒汉模式只是初始情况下 , 才会有线程不安全问题
一旦实例创建好了之后 , 此时就安全了
既然如此 , 后续再调用 getInstance 的时候就不应该再去加锁了
当线程已经安全了 , 我们再尝试进行加锁 , 其实就非常影响效率了
所以我们需要做到 : 该加锁的地方需要加锁 , 不该加锁的时候别乱加锁
我们只需要再加上一层 if 判定就 ok 了

// 单例模式的实现

// 懒汉模式
class SingletonLazy {
    // 先不着急实例化
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        // 进行判断
        // 为空->创建实例
        if(instance == null) {
            synchronized (SingletonLazy.class) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    // 构造方法同样也是 private 的
    private SingletonLazy() {}
}

public class Demo19 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
    }
}

image.png
image.png

外层 if 为了降低加锁的频率 , 降低锁冲突的概率 . 里层 if 才是真正判定是否要实例化
我们再举个栗子帮大家理清楚为什么要用双层判断
image.png
其中 , 一个线程读 , 一个线程写还存在内存可见性问题
image.png

// 单例模式的实现
// 懒汉模式
class SingletonLazy {
    // 先不着急实例化
    volatile private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        // 进行判断
        // 为空->创建实例
        if(instance == null) {
            synchronized (SingletonLazy.class) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    // 构造方法同样也是 private 的
    private SingletonLazy() {}
}

public class Demo19 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

    }
}

image.png

对于 volatile , 有一种大家认同比较多的说法是这样
volatile 在此处的用途 , 是禁止指令重排序
image.png


关于这里 , 还有可能问到对象存储在哪里的问题 , 我们也需要格外关注一下
image.png

单例模式的分析就到这里了 , 觉得有收获的请一键三连嗷~
在这里插入图片描述

  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 34
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加勒比海涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值