【多线程】多线程案例
✨个人主页: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 两个重要结论: 编辑 柯西中值定理: 如何用自己的语言理解极值呢? 极大值和极小值的类似,我们不再进行说明 极值点有什么特点吗&…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...

c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...