【多线程】多线程案例
✨个人主页: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 两个重要结论: 编辑 柯西中值定理: 如何用自己的语言理解极值呢? 极大值和极小值的类似,我们不再进行说明 极值点有什么特点吗&…...

redis 存储一个map 怎么让map中其中一个值设置过期时间,而不是过期掉整个map?
文章目录 redis 存储一个map 怎么让map中其中一个值设置过期时间,而不是过期掉整个map?Java 中 怎么 实现?方案一: Jedis方案二: Lettuce方案三: Redisson方案四: Jedisson方案五: RedisTemplate那种方式 效率最高 ?拓展:结语redis 存储一个map 怎么让map中其中一个值设置过…...

LeetCode:704. 二分查找
🍎道阻且长,行则将至。🍓 🌻算法,不如说它是一种思考方式🍀算法专栏: 👉🏻123 一、🌱704. 二分查找 题目描述:给定一个 n 个元素有序的ÿ…...

Java 到底是值传递还是引用传递?
C 语言是很多变成语言的母胎,包括 Java。对于 C 语言来说,所有的方法参数都是通过 “值” 传递的,也就是说,传递给被调用方法的参数值存放在临时变量中,而不是存放在原来的变量中。这就意味着,被调用的方法…...

Apollo 配置变更原理
我们经常用到apollo的两个特性:1.动态更新配置:apollo可以动态更新Value的值,也可以修改environment的值。2.实时监听配置:实现apollo的监听器ConfigChangeListener,通过onChange方法来实时监听配置变化。你知道apollo…...

聊聊「订单」业务的设计与实现
订单,业务的核心模块; 一、背景简介 订单业务一直都是系统研发中的核心模块,订单的产生过程,与系统中的很多模块都会高度关联,比如账户体系、支付中心、运营管理等,即便单看订单本身,也足够的复…...

血细胞智能检测与计数软件(Python+YOLOv5深度学习模型+清新界面版)
摘要:血细胞智能检测与计数软件应用深度学习技术智能检测血细胞图像中红细胞、镰状细胞等不同形态细胞并可视化计数,以辅助医学细胞检测。本文详细介绍血细胞智能检测与计数软件,在介绍算法原理的同时,给出Python的实现代码以及Py…...

高速PCB设计指南(十五)
掌握IC封装的特性以达到最佳EMI抑制性能 将去耦电容直接放在IC封装内可以有效控制EMI并提高信号的完整性,本文从IC内部封装入手,分析EMI的来源、IC封装在EMI控制中的作用,进而提出11个有效控制EMI的设计规则,包括封装选择、引脚结…...

GPT-4:我不是来抢你饭碗的,我是来抢你锅的
目录 一、GPT-4,可媲美人类 二、它和ChatGPT 有何差别? 01、处理多达2.5万字的长篇内容 02、分析图像的能力,并具有「幽默感」 03、生成网页 三、题外话 四、小结 GPT-4的闪亮登场,似乎再次惊艳了所有人。 看了GPT-4官方的…...

Scala环境安装【傻瓜式教程】
文章目录安装scala环境依赖Java环境安装下载sacla的sdk包安装Scala2.12检查安装是否成功idea配置idea安装scala插件项目配置新建maven项目添加框架支持选择scala创建测试类安装scala环境依赖 Java环境安装 sacla环境安装之前需要先确认Java jdk安装完成 java具体安装步骤略&…...

js实现一个简单的扫雷
目录先看下最终的效果:首先来分析一个扫雷游戏具有哪些功能分析完成后我们就开始一步步的实现1. 相关html和css2. 我们使用类来完成相应功能3. 之后我们则是要定义一个地图4. 对地图进行渲染5. 对开始按钮添加点击事件6. 现在我们可以实现鼠标左击扫雷的功能7. 给单…...