【多线程初阶】多线程案例之单例模式

文章目录

  • 前言
  • 1. 什么是单例模式
  • 2. 饿汉模式
  • 3. 懒汉模式 --- 单线程版
  • 4. 懒汉模式 --- 多线程版
  • 5. 懒汉模式 --- 多线程改进版
  • 总结


前言

本文主要给大家讲解多线程的一个重要案例 — 单例模式.

关注收藏, 开始学习吧🧐


1. 什么是单例模式

单例模式是一种很经典的设计模式, 那么什么叫做设计模式呢?

设计模式好比象棋中的 “棋谱”.
红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.
软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.

这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.

单例模式具体的实现方式有很多种写法, 在这里我们主要讲解 “饿汉”“懒汉” 两种.

2. 饿汉模式

核心思想: 类加载的同时, 创建实例.

class Singleton {
    private static Singleton instanse = new Singleton();

    public static Singleton getInstance() {
        return instanse;
    }

    private Singleton() {};
}

注意:

  1. private static Singleton instanse = new Singleton(); 被 static 修饰, 该属性是类的属性, JVM 中, 每个类的类对象只有唯一一份, 类对象里的这个成员自然也是唯一一份了.
  2. private Singleton() {}; 将构造方法设为 private, 就可以将外部的 new 操作给禁用掉.
  3. 此时, 在类内部把实例创建好, 同时禁止外部重新创造实例, 就可以保证单例的特性了.

在这里插入图片描述
由于构造方法设为 private, 导致 new 操作被禁用, 我们只能通过类方法 .getInstanse 来创建实例, 可以看到, 这样先后创建的 s1 和 s2 实例, 其实是同一个实例.

3. 懒汉模式 — 单线程版

核心思想: 类加载的时候不创建实例. 第一次使用的时候才创建实例.

class SingletonLazy {
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    private SingletonLazy() {};
}

注意点与饿汉模式差不多, 但是区别在于, 懒汉模式是 “非必要不创建”, 可以看到, instance 实例对象是在调用类方法时才创建的.

4. 懒汉模式 — 多线程版

现在问题来了, 上述两个模式, 是否能构保证线程安全呢?

多个线程下调用 getInstance 方法, 是否会出现问题呢?

回想一下我们之前讲解的线程不安全的几个原因. 可以推断饿汉模式下, 线程是安全的, 因为他只是读数据, 并没有进行写数据.

但是多线程下, 懒汉模式可能无法保证创建对象的唯一性, 线程不安全.

线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例.
一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改instance 了)

我们可以通过加锁, 利用 synchronized 关键字就可以改善这里的线程安全问题.

class SingletonLazy {
    private static SingletonLazy instance = null;

    public synchronized static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    private SingletonLazy() {};
}

5. 懒汉模式 — 多线程改进版

加锁其实是一个比较低效的操作, 因为他会造成阻塞等待, 非必要还是不要进行加锁.

以下代码在加锁的基础上, 做出了进一步改动:

  • 使用双重 if 判定, 降低锁竞争的频率.
  • 给 instance 加上了 volatile.
class SingletonLazy {
    private static volatile SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy() {};
}

理解双重 if 判定 / volatile:

加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候. 因此后续使用的时候, 不必再进行加锁了.

  • 外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
  • 同时为了避免 “内存可见性” 导致读取的 instance 出现偏差, 于是补充上 volatile .
  • 当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁, 其中竞争成功的线程, 再完成创建实例的操作.
  • 当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.
  • 有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同一把锁.

总结

✨ 本文讲解了线程安全下的单例模式, 由于饿汉模式只是读操作, 天生就是安全的, 而懒汉模式不是安全的, 因为有写操作, 我们通过加锁, 并利用双重 if 来减少不必要的加锁操作, 再使用 volatile 禁止指令重排序, 使其变得安全.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.

再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/57934.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Prometheus-各种exporter

一、 nginx-prometheus-exporter 1 nginx 配置 1.1 Nginx 模块支持 nginx 安装的时候需要有 nginx 的状态模块: stub_status 可通过如下命令检查 nginx -V 2>&1 | grep -o with-http_stub_status_module1.2 Nginx 配置文件配置 添加如下配置到自己 nginx 的配置文…

落地数字化管理,提升企业市场竞争力

数字化企业管理方案是一种利用数字技术和信息系统来提升企业管理效率和运营效果的策略。 潜在的数字化企业管理方案 1、企业资源规划(ERP)系统:建立一个集成的ERP系统来统一管理企业的各项业务流程,包括采购、销售、库存管理、财…

Java超级玛丽小游戏制作过程讲解 第一天 创建窗口

package com.sxt;import javax.swing.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener;public class MyFrame extends JFrame implements KeyListener {//设置窗口的大小为800*600public MyFrame() {this.setSize(800, 600);//设置窗口中显示this.setLo…

Cpp9 — map和set

map和set STL分为序列式容器(vector、list、deque)和关联式容器(map、set) 序列式容器:数据与数据之间没有很强的联系。(各个数据之间没什么关联)。底层为线性序列的数据结构,里面…

【云原生K8s】二进制部署单master K8s+etcd集群

一、实验设计 mater节点master01192.168.190.10kube-apiserver kube-controller-manager kube-scheduler etcd node节点node01192.168.190.20kubelet kube-proxy docker (容…

elementUI全屏loading的使用(白屏的解决方案)

官网中有使用方法&#xff0c;但是我实际上手之后会出现白屏&#xff0c;解决办法如下&#xff1a; <el-button type"text" size"small" click"delRow(scope)"> 删除</el-button>loading: false, // loading 动画loadingInstance…

ubuntu下,在vscode中使用platformio出现 Can not find working Python 3.6+ Interpreter的问题

有一段时间没有使用platformio了&#xff0c;今天突然使用的时候&#xff0c;发现用不了&#xff0c;报错&#xff1a; Ubuntu PlatformIO: Can not find working Python 3.6 Interpreter. Please install the latest Python 3 and restart VSCode。 上网一查&#xff0c;发现…

【NLP概念源和流】 06-编码器-解码器模型(6/20 部分)

一、说明 在机器翻译等任务中,我们必须从一系列输入词映射到一系列输出词。读者必须注意,这与“序列标记”不同,在“序列标记”中,该任务是将序列中的每个单词映射到预定义的类,如词性或命名实体任务。 作者生成 在上面的

基于回溯算法实现八皇后问题

八皇后问题是一个经典的计算机科学问题&#xff0c;它的目标是将8个皇后放置在一个大小为88的棋盘上&#xff0c;使得每个皇后都不会攻击到其他的皇后。皇后可以攻击同一行、同一列和同一对角线上的棋子。 一、八皇后问题介绍 八皇后问题最早由国际西洋棋大师马克斯贝瑟尔在18…

计算机视觉与图形学-神经渲染专题-第一个基于NeRF的自动驾驶仿真平台

如今&#xff0c;自动驾驶汽车可以在普通情况下平稳行驶&#xff0c;人们普遍认识到&#xff0c;真实的传感器模拟将在通过模拟解决剩余的极端情况方面发挥关键作用。为此&#xff0c;我们提出了一种基于神经辐射场&#xff08;NeRF&#xff09;的自动驾驶模拟器。与现有作品相…

7_分类算法—逻辑回归

文章目录 逻辑回归&#xff1a;1 Logistic回归&#xff08;二分类问题&#xff09;1.1 sigmoid函数1.2 Logistic回归及似然函数&#xff08;求解&#xff09;1.3 θ参数求解1.4 Logistic回归损失函数1.5 LogisticRegression总结 2 Softmax回归&#xff08;多分类问题&#xff0…

Nginx安装和Nginx配置虚拟主机

Nginx安装 源码包获取地址&#xff1a;http://nginx.org/download/ RPM包获取地址&#xff1a;http://nginx.org/packages/centos/7Server/x86_64/RPMS/ RPM安装 这里选择的RPM包是 nginx-1.22.0-1.el7.ngx.x86_64.rpm [rootlocalhost ~]# yum install nginx-1.22.0-1.el7.…

【项目 进程12】2.25 sigprocmask函数使用 2.26sigaction信号捕捉函数 2.27SIGCHILD信号

文章目录 2.25 sigprocmask函数使用2.26 sigaction信号捕捉函数内核实现信号捕捉的过程信号捕捉特性 2.27SIGCHILD信号 2.25 sigprocmask函数使用 阻塞信号集有时称作信号掩码。 联想&#xff1a;fcntl函数可以修改fd属性。 ./sigprocmask & //将程序设置为后台运行&…

深度学习论文: Towards Total Recall in Industrial Anomaly Detection及其PyTorch实现

深度学习论文: Towards Total Recall in Industrial Anomaly Detection及其PyTorch实现 Towards Total Recall in Industrial Anomaly Detection PDF: https://arxiv.org/pdf/2106.08265.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代码: https://…

2.4G芯片XL2408开发板,SOP16封装,芯片集成1T 8051内核单片机

XL2408开发板可用于2.4G芯片XL2408开发板的开发调试。XL2408烧录仿真需要使用WS_LINK。XL2408开发板烧录仿真需要接4根线&#xff1a;PA13:DIO&#xff0c;PA14:CLK&#xff0c;VCC&#xff0c;GND。 XL2408芯片集成射频收发机、频率收生器、晶体振荡器、调制解调器等功能模块,…

CentOS 7.6使用yum安装stress,源码安装stree-ng 0.15.06,源码安装sysstat 12.7.2

cat /etc/redhat-release看到操作系统的版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到内核版本是3.10.0-957.21.3.el7.x86_64 yum install stress sysstat -y安装stress和sysstat。 使用pidstat -u 5 1没有%wait项&#xff1a; 原因是CentOS 7仓…

1分钟解决github push/pull报错443

1.打开https://www.ipaddress.com/ 2.复制如图IP地址 3.文件夹打开C:\Windows\System32\drivers\etc&#xff0c;复制hosts文件&#xff0c;粘贴到桌面 4.在桌面用记事本打开复制过来的hosts 5.在末尾加上一行&#xff0c;IP写刚才复制的 6.复制桌面的hosts,粘贴回C:\Window…

剑指offer48.最长不含重复字符的子字符串

我一开始的想法是创建一个大小为26的int数组&#xff0c;下标为0对应的是‘a&#xff0c;25对应的是’z&#xff0c;然后一开始都赋为-1&#xff0c;用一个for循环从头遍历这个字符串&#xff0c;通过char c s.charAt(i)获得字符&#xff0c;然后c-97&#xff0c;就是它对应的…

windows系统之WSL 安装 Ubuntu

WSL windows10 以上才有这个wsl功能 WSL&#xff1a; windows Subsystem for Linux 是应用于Windows系统之上的Linux子系统 作用很简单&#xff0c;可以在Windows系统中获取Linux系统环境&#xff0c;并完全直连计算机硬件&#xff0c;无需要通过虚拟机虚拟硬件 Windows10的W…

swift - 如何在数组大小更改后刷新 ForEach 显示元素的数量(SwiftUI、Xcode 11 Beta 5)

我正在尝试实现一个 View &#xff0c;该 View 可以在内容数组的大小发生变化时更改显示项目的数量(由 ForEach 循环创建)&#xff0c;就像购物应用程序可能会在用户下拉刷新后更改其可用项目的数量一样 这是我到目前为止尝试过的一些代码。如果我没记错的话&#xff0c;这些适…