【多线程】多线程案例

✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇
✨每日一语:we can not judge the value of a moment until it becomes a memory.
目 录
- 🍝一. 单例模式
- 🍤1. 饿汉模式实现
- 🦪2. 懒汉模式实现
- 🍲二. 阻塞式队列
🍝一. 单例模式
单例模式是校招中最常考的设计模式之一.
啥是设计模式?
设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.
软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.
单例模式目的:有些对象,在一个程序中应该只有唯一一个实例,就可以使用单例模式。单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例。
一个程序中应该只有唯一一个实例是程序猿来保证的,不一定靠谱,于是在单例模式下借助语法,强行限制咱们不能创建多个实例。
Java 里的单例模式,有很多种实现方式,主要介绍两个大类:饿汉模式 和 懒汉模式
饿汉模式:程序启动,则立即创建实例
懒汉模式:程序启动,先不着急创建实例,等到真正用的时候,再创建
单例模式具体实现方式:
🍤1. 饿汉模式实现
class Singleton{private static Singleton instance = new Singleton();public static Singleton getInstance(){return instance;}//构造方法设为私有!其他的类想来 new 就不行了private Singleton(){ }
}public class Demo19 {public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton isstance2 = Singleton.getInstance();System.out.println(instance == isstance2);}
}
- static 静态,实际效果和字面意思没有任何关联,实际的含义是:类属性 / 类方法。
- 类属性就长在类对象上,类对象在整个程序中只有唯一一个实例!!(JVM 保证的)
- 在静态方法中,后续我们需要使用这个实例,统一基于 getInstance 方法来获取,实例独苗,不需要再 new 了(new 也会失败)
- 使用静态成员表示实例(唯一性) + 让构造方法为私有(堵住了 new 创建新实例的口子)
按照现在的代码,当 Singleton 类被加载的时候,就会执行到此处的实例化操作!!实例化时机非常早!(非常迫切的感觉)
🦪2. 懒汉模式实现
class Singletonlazy{private static Singletonlazy instance = null;public static Singletonlazy getInstance(){if(instance == null){instance = new Singletonlazy();}return instance;}private Singletonlazy(){ }
}
- 第一步并没有创建实例!
- 首次调用 getInstance 才会创建实例!
上面俩种模式还涉及到线程安全。
饿汉模式是线程安全的,多线程涉及 getInstance ,只是多线程读,没事儿懒汉模式是线程不安全的,有的地方在读,有的地方在写,一旦实例创建好了之后,后续 if 条件就进不去了,此时也就全是读操作了,也就线程安全了。
如何解决懒汉模式线程不安全?
方法就是加锁:
synchronized (Singletonlazy.class) {if (instance == null) {instance = new Singletonlazy();}
}
把读和写两个步骤打包在一起,保证读 判定 修改 这组操作是原子的!
懒汉模式,只是初始情况下,才会有线程不安全问题,一旦实例创建好了之后,此时就安全了!既然如此,后续在调用 getlnstance 的时候就不应该再尝试加锁了!当线程安全之后,再尝试加锁,就非常影响效率了。
如上代码我们只需要再嵌套一个 if 判定即可
public static Singletonlazy getInstance(){if (instance == null) {synchronized (Singletonlazy.class) {if (instance == null) {instance = new Singletonlazy();}}}return instance;
}
注意!不要用单线程的理解方式来看待多线程代码!如果是单线程,连续两个一样的 if 判定,毫无意义!但是多线程就不是了,尤其是中间隔了个加锁操作!
- 加锁操作可能就涉及到阻塞,前面的 if 和后面的 if 中间可能就隔了个 “沧海桑田”。
- 外层 if 判定当前是否已经初始化好,如果未初始化好,就尝试加锁,如果是已初始化好,那么就直接往下走。
- 里层的 if 是在多个线程尝试初始化,产生了锁竞争,这些参与锁竞争的线程,拿到锁之后,再进一步确认,是否真的要初始化。
理解双重 if 判定:最核心的目标,就是降低锁竞争的概率
当多线程首次调用 getInstance,发现 instance 为 null, 于是又继续往下执行来竞争锁,其中竞争成功的线程, 再完成创建实例的操作。当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例
- 有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同一把锁.
- 其中线程1 率先获取到锁, 此时线程1 通过里层的 if (instance == null) 进一步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来.
- 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过里层的 if (instance == null) 来确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了.
- 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了, 从而不再尝试获取锁了. 降低了开销.
很多线程尝试读,这样的读,是否会被优化成读寄存器呢?
第一个线程读,把内存的数据读到寄存器了,第二个线程也去读,会不会就直接重复利用上述寄存器的结果呢?由于每个线程有自己的上下文,每个线程有自己的寄存器内容,因此按理来说是不会出现优化的,但是实际上不一定,
因此在这个场景下,给 instance 加上 volatile 是最稳健的做法!
volatile private static Singletonlazy instance = null;
对懒汉模式的总结:
加锁双重 if 判定(外层 if 为了降低加锁的频率,降低锁冲突的概率,里层 if 才是真正判定是否要实例化)volatile
🍲二. 阻塞式队列
阻塞队列是什么?
阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
- 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
- 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
- 阻塞队列:能够保证 “线程安全”
- 无锁队列:也是一种线程安全的队列,实现内部没有使用锁,更高效,消耗更多的 CPU 资源。
- 消息队列:在队列中涵盖多种不同 “类型” 元素,取元素的时候可以按照某个类型来取,做到针对该类型的 “先进先出” (甚至说会把消息队列作为服务器,单独部署)
阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型
生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取。
优点:
- 可以更好地做到 “解耦合”。
A 直接给 B 发送数据,就是耦合性比较强,开发 A 的时候就得考虑 B 是如何接收的,开发 B 的时候就得考虑 A 是如何发送的。极端情况下 A 出现问题挂了 可以能也造成 B 出现问题导致 B 也挂了,反之 B 出现了问题,也会牵连 A 导致 A 挂了。
于是在阻塞队列的影响下,A 和 B 不再直接交互
开发阶段:A 只用考虑自己和队列如何交互,B 也只用考虑自己和队列如何交互,A 和 B 之间都不需要知道对方的存在。
部署阶段:A 如果挂了,对 B 没有任何影响;B 如果挂了,对 A 没有任何影响。
- 能够做到 “削峰填谷” ,提高整个系统抗风险能力。
程序猿无法控制外网有多少个用户在访问 A,当出现极端情况,外网访问请求大量涌入的时候,A 把所有请求的数据一并转让给 B 的时候,B 就容易扛不住而挂掉。
在阻塞队列的影响下
多出来的压力队列承担了,队列里多存一会儿数据就行了,即使 A 的压力比较大,B 仍按照固定的频率来取数据。
标准库中的阻塞队列
在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可
- BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
- put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
- BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
生产者消费者模型:
public class Demo20 {public static void main(String[] args) {BlockingDeque<Integer> queue = new LinkedBlockingDeque<>();Thread customer = new Thread(()->{while (true){try {int value = queue.take();System.out.println("消费元素:" + value);} catch (InterruptedException e) {e.printStackTrace();}}});customer.start();Thread producer = new Thread(()->{int n = 0;while (true){try {System.out.println("生产元素:" + n);queue.put(n);n++;Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}});producer.start();}
}
运行结果演示图:

阻塞队列实现:
- 自己模拟实现一个阻塞队列
- 基于数组的方式来实现队列
- 两个核心方法:1. put 入队列; 2. take 出队列
class MyBlockingQueue {// 假定最大是 1000 个元素,当然也可以设定成可配置的private int[] items = new int[1000];//队首的位置private int head = 0;//队尾的位置private int tail = 0;//队列的元素个数private int size = 0;//入队列public void put (int value) throws InterruptedException {synchronized (this) {while (size == items.length) {//队列已满,继续等待this.wait();}items[tail] = value;tail++;if (tail == items.length) {//注意 如果 tail 到达数组末尾,就需要从头开始tail = 0;}size++;//即使没人在等待,多调用几次 notify 也没事,没负面影响this.notify();}}//出队列public Integer take() throws InterruptedException {int ret = 0;synchronized (this) {while (size == 0) {//队列为空,就等待this.wait();}ret = items[head];head++;if (head == items.length) {head = 0;}size--;this.notify();}return ret;}
}public class Demo21 {public static void main(String[] args) throws InterruptedException {MyBlockingQueue queue = new MyBlockingQueue();queue.put(100);queue.take();}
}
- 入队列中的 wait 和出队列中的 notify 对应,满了之后,入队列就要阻塞等待,此时在取走元素之后,就可以尝试唤醒了。
- 入队列中的 notify 和出队列中的 wait 对应,队列为空,也要阻塞,此时在插入成功之后,队列就不为空了,就能够把 take 的等待唤醒。
- 一个线程中无法做到又等待又唤醒
- 阻塞之后,就要唤醒,阻塞和唤醒之间是沧海桑田,虽然按照当下代码是有元素插入成功了,条件不成立,等待结束。但是更稳妥的做法是把 if 换成 while ,在唤醒之后,再判断一次条件!万一条件又成立了呢?万一接下来要继续阻塞等待呢?
测试代码:
public class Demo21 {public static void main(String[] args) {MyBlockingQueue queue = new MyBlockingQueue();Thread customer = new Thread(()->{while (true){int value = 0;try {value = queue.take();System.out.println("消费:" + value);Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}});customer.start();Thread producer = new Thread(()->{int value = 0;while (true){try {queue.put(value);System.out.println("生产:" + value);value++;} catch (InterruptedException e) {e.printStackTrace();}}});producer.start();}
}
延缓了消费代码,也可以把生产代码延缓,调用 sleep 即可
- 延缓消费代码
- 延缓生产代码
相关文章:
【多线程】多线程案例
✨个人主页:bit me👇 ✨当前专栏:Java EE初阶👇 ✨每日一语:we can not judge the value of a moment until it becomes a memory. 目 录🍝一. 单例模式🍤1. 饿汉模式实现🦪2. 懒汉模…...
【IoT】嵌入式驱动开发:IIC子系统
IIC有三种接口实现方式 三种时序对比: 图1 IIC子系统组成 图2 图3 IIC操作流程 设备端 1.i2c_get_adapter 2.i2c_new_device(相当于register设备) 3.I2c_put_adapter 驱动端 1.填充i2c_driver 2.i2c_add_driver(相当于register驱动) 3.在probe中建立访问方式 client相…...
DJ2-4 进程同步(第一节课)
目录 2.4.1 进程同步的基本概念 1. 两种形式的制约关系 2. 临界资源(critical resource) 3. 生产者-消费者问题 4. 临界区(critical section) 5. 同步机制应遵循的规则 2.4.2 硬件同步机制 1. 关中断 2. Test-and-Set …...
AI独立开发者:一周涨粉8万赚2W美元;推特#HustleGPT GPT-4创业挑战;即刻#AIHackathon创业者在行动 | ShowMeAI周刊
👀日报&周刊合辑 | 🎡生产力工具与行业应用大全 | 🧡 点赞关注评论拜托啦! 这是ShowMeAI周刊的第7期。聚焦AI领域本周热点,及其在各圈层泛起的涟漪;拆解AI独立开发者的盈利案例,关注中美AIG…...
不要迷信 QUIC
很多人都在强调 QUIC 能解决 HoL blocking 问题,不好意思,我又要泼冷水了。假设大家都懂 QUIC,不再介绍 QUIC 的细节,直接说问题。 和 TCP 一样,QUIC 也是一个基于连接的,保序的可靠传输协议,T…...
【28】Verilog进阶 - RAM的实现
VL53 单端口RAM 1 思路 简简单单,读取存储器单元值操作即可 2 功能猜想版 说明: 下面注释就是我对模块端口信号 自己猜测的理解。 因为题目并没有说清楚,甚至连参考波形都没有给出。 唉,这就完全是让人猜测呢,如果一点学术背景的人来刷题,指定不容易!! 好在,我有较为…...
【MySQL】聚合查询
目录 1、前言 2、插入查询结果 3、聚合查询 3.1 聚合函数 3.1.1 count 3.1.2 sum 3.1.3 avg 3.1.4 max 和 min 4、GROUP BY 子句 5、HAVING 关键字 1、前言 前面的内容已经把基础的增删改查介绍的差不多了,也介绍了表的相关约束, 从本期开始…...
初时STM32单片机
目录 一、单片机基本认知 二、STM系列单片机命名规则 三、标准库与HAL库区别 四、通用输入输出端口GPIO 五、推挽输出与开漏输出 六、复位和时钟控制(RCC) 七、时钟控制 八、中断和事件 九、定时器介绍 一、单片机基本认知 单片机和PC电脑相比…...
debian部署docker(傻瓜式)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 debian10部署dockerdebian10部署docker(傻瓜式)一、准备工作二、**使用 APT 安装,注意要先配置apt网络源**1.配置网络源2.官方下载三、安装…...
JS判断是否为base64字符串如何转换为图片src格式
需求背景 : 如何判断后端给返回的 字符串 是否为 base-64 位 呢 ? 以及如果判断为是的话,如何给它进行转换为 img 标签可使用的那种 src 格式 呢 ? 1、判断字符串是否为 base64 以下方法,可自行挨个试试,…...
【SpringMVC】SpringMVC方式,向作用域对象共享数据(ModelAndView、Model、map、ModelMap)
个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ 向域对象共享数据一、使用 原生ServletAPI二、…...
本科课程【移动互联网应用开发(Android开发)】实验3 - Activity及数据存储
大家好,我是【1+1=王】, 热爱java的计算机(人工智能)渣硕研究生在读。 如果你也对java、人工智能等技术感兴趣,欢迎关注,抱团交流进大厂!!! Good better best, never let it rest, until good is better, and better best. 近期会把自己本科阶段的一些课程设计、实验报…...
为何在 node 项目中使用固定版本号,而不使用 ~、^?
以语雀 文档为准 使用 ~、^ 时吃过亏希望版本号掌握在自己手里,作者自己升级(跟随官方进行升级,就算麻烦作者,也不想麻烦使用者)虽然 pnpm 很好用,但是不希望在项目中用到(临时性解决问题可以选…...
leetcode -- 876.链表的中间节点
文章目录🐨1.题目🐇2. 解法1-两次遍历🍀2.1 思路🍀2.2 代码实现🐁3. 解法2-快慢指针🌾3.1 思路🌾3.2 **代码实现**🐮4. 题目链接🐨1.题目 给你单链表的头结点head&#…...
企业网络安全防御策略需要考虑哪些方面?
随着企业数字化转型的加速,企业网络安全面临越来越多的威胁。企业网络安全不仅仅关乎企业数据的安全,还关系到企业的声誉和利益,因此,建立全面的网络安全防御策略至关重要。 企业网络安全防御策略的实现需要考虑以下几个方面&…...
文心一言 vs. GPT-4 —— 全面横向比较
文心一言 vs. GPT-4 —— 全面横向比较 3月15日凌晨,OpenAI发布“迄今为止功能最强大的模型”——GPT-4。我第一时间为大家奉上了体验报告《OpenAI 发布GPT-4——全网抢先体验》。 时隔一日,3月16日下午百度发布大语言模型——文心一言。发布会上&…...
【进阶数据结构】二叉搜索树经典习题讲解
🌈感谢阅读East-sunrise学习分享——[进阶数据结构]二叉搜索树 博主水平有限,如有差错,欢迎斧正🙏感谢有你 码字不易,若有收获,期待你的点赞关注💙我们一起进步 🌈我们在之前已经学习…...
PyTorch 之 神经网络 Mnist 分类任务
文章目录一、Mnist 分类任务简介二、Mnist 数据集的读取三、 Mnist 分类任务实现1. 标签和简单网络架构2. 具体代码实现四、使用 TensorDataset 和 DataLoader 简化本文参加新星计划人工智能(Pytorch)赛道:https://bbs.csdn.net/topics/613989052 一、Mnist 分类任…...
如何实现用pillow库来实现给图片加滤镜?
使用Pillow库可以非常容易地给图片加滤镜。Pillow库是Python图像处理的一个强大库,提供了多种滤镜效果,如模糊、边缘检测、色彩增强等。 下面是使用Pillow库实现给图片加滤镜的简单步骤: 安装Pillow库:首先需要安装Pillow库。可…...
微分中值定理
极值 目录 极值 费马引理 编辑 罗尔定理 拉格朗日中值定理 例题: 例2 例3 两个重要结论: 编辑 柯西中值定理: 如何用自己的语言理解极值呢? 极大值和极小值的类似,我们不再进行说明 极值点有什么特点吗&…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...
门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...





