【JavaEE精炼宝库】多线程(5)单例模式 | 指令重排序 | 阻塞队列
目录
一、单例模式:
1.1 饿汉模式:
1.2 懒汉模式:
1.2.1 线程安全的懒汉模式:
1.2.2 线程安全的懒汉模式的优化:
二、指令重排序
三、阻塞队列
3.1 阻塞队列的概念:
3.2 生产者消费者模型:
3.3 标准库中的阻塞队列:
3.4 阻塞队列实现:
一、单例模式:
单例模式是校招中最常考的设计模式之一。
设计模式是什么?
设计模式好比象棋中的 "棋谱"。红方当头炮,黑方马来跳。针对红方的⼀些走法,黑方应招的时候有一些固定的套路。按照套路来走局势就不会吃亏。软件开发中也有很多常见的 "问题场景" 针对这些问题场景,大佬们总结出了一些固定的套路。按照这个套路来实现代码,也不会吃亏。大佬们为我们操碎了心。
单例模式能保证某个类在程序中只存在唯⼀⼀份实例,而不会创建出多个实例。这一点在很多场景上都需要。比如 JDBC 中的 DataSource 实例就只需要一个。
单例模式具体的实现方式有很多。最常见的是 "饿汉" 和 "懒汉" 两种。
1.1 饿汉模式:
类加载的同时,创建实例。
• 案例代码实现:
核心思想就是把构造方法设置为 private ,再把实例用 static 修饰。程序一运行,实例就被创建了,Singleton 类外面想要得到这个对象,只能通过 getInstance 来得到,所以能保证这个实例只被创建一次。
class Singleton{private static Singleton instance = new Singleton();//static 要记得加private Singleton(){}//这里要设置成 private,防止创建出多个实例public static Singleton getInstance(){return instance;}
}
1.2 懒汉模式:
类加载的时候不创建实例。第一次使用的时候才创建实例。
在计算机中 “懒” 是指高效的意思。这样如果后续这个类没有使用到,就可以把创建这个实例的损耗节省下来。
• 案例代码实现:
class SingletonLaze{private static SingletonLaze instance = null;private SingletonLaze(){}public static SingletonLaze getInstance(){if(instance == null){instance = new SingletonLaze();}return instance;}
}
到这里饿汉模式和懒汉模式的代码就已经大体编写完毕了。
请友友们思考一个问题:在多线程的情况下,上面的两种模式会出现线程不安全的情况嘛?
答:饿汉模式是线程安全的,懒汉模式是线程不安全的。
线程安全问题发生在首次创建实例时。如果在多个线程中同时调用 getInstance 方法,就可能导致创建出多个实例(虽然后续会被回收成一个,但是多个案例是实实在在被创建出来了,如果一个案例要使用 100G内存 ,会导致系统卡死的)。至于饿汉模式,在类加载的时候实例就已经被创建了,自然不存在线程安全问题。
1.2.1 线程安全的懒汉模式:
怎么解决懒汉模式的线程安全问题呢?
答:加锁。
加上 synchronized 可以改善这里的线程安全问题。
改进的案例代码如下:
class SingletonLaze {private static SingletonLaze instance = null;private static Object locker = new Object();private SingletonLaze() {}public static SingletonLaze getInstance() {synchronized (locker) {if(instance == null){//2 锁能不能加在 if 的里面instance = new SingletonLaze();}return instance;}}
}
这里友友们思考一下:锁能不能加在代码 2 的地方?
答:不能,如果多个线程同时进入的话,都能进入 if ,创建实例,那么锁就白加了。
到这里面试官可能还会问你,还能不能优化一下呢?
1.2.2 线程安全的懒汉模式的优化:
这里我们发现,只有在刚开始创建第一个实例的时候存在线程不安全的问题,创建完后,就和饿汉模式一样不会存在线程安全问题,这时代码还是一直加锁的话,会影响程序的效率,因为锁本身就是一个重量级操作。因此我们要在加锁的基础上,进一步改动。
• 使用双重 if 判定,降低锁竞争的频率。
• 给 instance 加上了 volatile。避免出现内存可见性导致的问题(这里概率很小)和指令重排序问题(大头)。
最终的代码如下:
class SingletonLaze {private static volatile SingletonLaze instance = null;private static Object locker = new Object();private SingletonLaze() {}public static SingletonLaze getInstance() {if (instance == null) {synchronized (locker) {if (instance == null) {instance = new SingletonLaze();}}}return instance;}
}
在多线程中,许多在单线程看起来毫无意义的操作,在多线程就可能有不同的作用,只是代码的编写恰好相同而已。第一个 if 是为了判断要不要加锁,第二个 if 是为了判断要不要创建对象。
二、指令重排序
指令重排序也是编译器的一种优化策略。
我们写的代码最终编译成了一系列的二进制指令。正常来说,CPU 是按照顺序,一条一条执行的。但是编译器比较智能,会根据实际情况,生成的二进制指令的执行顺序和我们最初写的代码顺序可能会存在差别,调整顺序最主要的目的就是提高效率。(前提要保证逻辑是等价的)
就好比如:田忌赛马。不同的执行顺序,产生的结果是截然不同的。
单线程下编译器的指令重排序一般都是没有问题的,但是在多线程的情况下,编译器的判定就可能不是那么的准确了。
在懒汉模式的优化那里如果不加上 volatile 关键字(防止指令重排序)可能会发生什么事情呢?
答:
instance = new SingletonLaze();
这一行代码,大体上可以分为如下三个步骤:
1. 申请内存空间。
2. 调用构造方法。(对内存空间进行初始化)
3. 将此时内存空间的地址,赋值给 instance 引用。
在指令重排序的优化策略下,上述执行的过程可能是1,2,3。也可能是1,3,2。如果是1,3,2的话,在多线程的情况下,可能就会有 bug 。在多线程的情况下,如果在第一个抢到锁的线程,创建实例,执行完1,3 再到 2 的这个过程中,如果有新的线程进来,那么在最外层 if 判断时,就会认为 instance 已经有实例了,直接返回一个空引用,这时正好这个引用被进行 ' . ' 操作,就会出现 bug。
上述谈到的指令重排序涉及到的 bug 是很难重现的,本身就是一个小概率事件。最好还是加上,如果出现问题,可能会带走年终奖😭。
三、阻塞队列
3.1 阻塞队列的概念:
队列我们已经很熟悉了。普通队列和优先级队列是线程不安全的。阻塞队列是一种特殊的队列。也遵守 "先进先出" 的原则。阻塞队列是一种线程安全的数据结构,并且具有以下特性:
• 当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素。
• 当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素。
阻塞队列的一个典型应用场景就是 "生产者消费者模型"。这是一种非常典型的开发模型。
3.2 生产者消费者模型:
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者,生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取。
生产者消费者模型,在开发中主要有两方面的意义:
• 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。(削峰填谷)
• 阻塞队列也能使生产者和消费者之间解耦。
3.3 标准库中的阻塞队列:
在 Java 标准库中内置了阻塞队列。如果我们需要在一些程序中使用阻塞队列,直接使用标准库中的即可。
• BlockingQueue:接口
• ArrayBlockingQueue类:数组。
• LinkedBlockingQueue类 :链表。
• PriorityBlockingQueue类:堆 。
可以看到下面的三个类都实现了 BlockingQueue 接口。阻塞队列的使用方法如下:
• put 方法用于阻塞队列的入队列,take 用于阻塞队列的出队列(put、take 带有阻塞功能)。
• BlockingQueue 也有 offer, poll, peek 等方法,但是这些方法不带有阻塞特性。
案例演示:
import java.util.concurrent.*;
public class demo1 {public static void main(String[] args) {BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(100);//设置阻塞队列的容量为 100Thread producer = new Thread(() -> {//生产者for (int i = 1; i < 100000; i++) {try {System.out.println("生产:" + i);queue.put(i);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"生产者");producer.start();Thread customer = new Thread(() -> {//消费者for (int i = 1; i < 100000; i++) {try {Thread.sleep(1000);int tmp = queue.take();System.out.println("消费:" + tmp);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"消费者");customer.start();}
}
案例效果如下:
可以看到由于消费者被 sleep 了 1 秒,所以生产者马上就生产到了 100,到了 100 后由于阻塞队列具有阻塞功能,所以后续程序只能消费一个生产一个。
3.4 阻塞队列实现:
使用 synchronized 进行加锁控制。
要实现的功能有:
• put 插入元素的时候,判定如果队列满了,就进行 wait。(注意,要在循环中进行 wait。被唤醒时不一定队列就不满了,因为同时可能是唤醒了多个线程)。
• take 取出元素的时候,判定如果队列为空,就进行 wait 。(也是循环 wait)
具体的代码实现如下:
参数都在代码里面已经标好了,这里就不再赘述。唯一注意点就是在 wait 的条件语句使用 while 而不是 if。
public class MyBlockingQueue {private String[] elems = null;private volatile int tail = 0;//尾指针private volatile int head = 0;//头指针private volatile int size = 0;//大小public MyBlockingQueue(int capacity) {elems = new String[capacity];}/*** 把元素 elem 加入到队列中** @param elem*/public void put(String elem) throws InterruptedException {synchronized (this) {//保证线程安全while (size >= elems.length) {//最好写成 while//队列满的情况,阻塞this.wait();}//普通的队列操作elems[tail] = elem;size++;tail++;if (tail >= elems.length) {tail = 0;}this.notify();//唤醒 take }}/*** 从队列中取出 elem 元素* @return* @throws InterruptedException*///takepublic String take() throws InterruptedException {synchronized (this) {while (size == 0) {//当队列为空时,阻塞this.wait();}String result = elems[head];size--;head++;if (head >= elems.length) {head = 0;}this.notify();return result;//唤醒 put }}
}
演示效果:
可以看到和上面使用标准库中的阻塞队列功能基本一致。
结语:
其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。
相关文章:
【JavaEE精炼宝库】多线程(5)单例模式 | 指令重排序 | 阻塞队列
目录 一、单例模式: 1.1 饿汉模式: 1.2 懒汉模式: 1.2.1 线程安全的懒汉模式: 1.2.2 线程安全的懒汉模式的优化: 二、指令重排序 三、阻塞队列 3.1 阻塞队列的概念: 3.2 生产者消费者模型…...
[图解]《分析模式》漫谈03-Party是什么
1 00:00:00,790 --> 00:00:03,930 今天我们来看一下,Party是什么 2 00:00:05,710 --> 00:00:07,470 当然我们这里说的不是政治的 3 00:00:07,880 --> 00:00:08,350 Party 4 00:00:09,230 --> 00:00:11,110 是《分析模式》里面的一个用词 5 00:00:14…...
【Numpy】一文向您详细介绍 np.abs()
【Numpy】一文向您详细介绍 np.abs() 下滑即可查看博客内容 🌈 欢迎莅临我的个人主页 👈这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地!🎇 🎓 博主简介:985高校的普通本硕,曾…...
【AI绘画】Stable Diffusion 3开源
Open Release of Stable Diffusion 3 Medium 主要内容 Stable Diffusion 3是Stability AI目前为止最先进的文本转图像开放源代码算法。 这款模型的小巧设计使其完美适合用于消费级PC和笔记本电脑,以及企业级图形处理单元上运行。它已经满足了标准化的文字转图像模…...
使用ant-design/cssinjs向plasmo浏览器插件的内容脚本content中注入antd的ui组件样式
之前写过一篇文章用来向content内容脚本注入antd的ui:https://xiaoshen.blog.csdn.net/article/details/136418199,但是方法就是比较繁琐,需要将antd的样式拷贝出来,然后贴到一个单独的css样式文件中,然后引入到内容脚…...
南京威雅学校:初中转轨国际化教育,她们打开了成长的另一种可能
“上了大学就轻松了。” 又是一年高考季,每每回想起十八岁前那些没日没夜埋头学习的日子,已经为人父母的你是不是也忍不住想要孩子气地吐槽一句,“骗人”——人不会在一场考试后瞬间长大,试卷里也没有人生的全部答案。 三年前&a…...
Linux | 标准IO编程
Linux | 标准IO编程 时间:2024年6月8日23:03:43 文章目录 `Linux` | 标准`IO`编程1.标准`IO`编程1-1.流的打开函数fopen()1-2.流的关闭函数fclose()1-3.错误处理函数perror()函数strerror()errno 变量总结1-4.流的读写1-4-1.按字符(字节)输入/输出实例1-4-2.按行输入/输出1-…...
从ES的JVM配置起步思考JVM常见参数优化
目录 一、真实查看参数 (一)-XX:PrintCommandLineFlags (二)-XX:PrintFlagsFinal 二、堆空间的配置 (一)默认配置 (二)配置Elasticsearch堆内存时,将初始大小设置为…...
milvus的GPU索引
前言 milvus支持多种GPU索引类型,它能加速查询的性能和效率,特别是在高吞吐量,低延迟和高召回率的场景。本文我们将介绍milvus支持的各种GPU索引类型以及它们适用的场景、性能特点。 下图展示了milvus的几种索引的查询性能对比,…...
CleanMyMac2024最新免费电脑Mac系统优化工具
大家好,我是你们的好朋友——软件评测专家,同时也是一名技术博主。今天我要给大家种草一个超级实用的Mac优化工具——CleanMyMac! 作为一个长期使用macOS的用户,我深知系统运行时间长了,缓存文件、日志、临时文件等都会…...
catia/delmia的快捷图标模式最多12个
这儿最多显示12个 根据官方文档 If you installed a configuration containing more than 12 workbenches (such as the "AL2" configuration), only the first 12 workbenches are displayed in the Favorites list. The other workbenches do not appear in the l…...
磁盘性能概述与磁盘调度算法
目录 1. 磁盘性能概述 1. 数据传输速率 2. 寻道时间 3. 旋转延迟 4. 平均访问时间 2. 早期的磁盘调度算法 1. FIFO(First-In-First-Out)调度算法 2. SSTF(Shortest Seek Time First)调度算法 3. SCAN(Elevator…...
chrome浏览器设置--disable-web-security解决跨域
在开发人员于后台进行接口测试的时候,老是遇到跨域问题,这时前端总是会让后台添加跨域请求头来允许跨域请求,今天介绍一个简单的方法跨过这一步操作的设置。 –disable-web-security参数,禁用同源策略,利于开发人员本…...
Android中蓝牙设备的状态值管理
在Android中,蓝牙状态可以通过多种方式来描述,主要包括蓝牙适配器状态、蓝牙设备连接状态以及蓝牙广播状态,其关键的蓝牙状态实现类有BluetoothAdapter、BluetoothDevicePairer、BluetoothDevice、BluetoothProfile,详细介绍如下&…...
关于ReactV18的页面跳转传参和接收
一、使用路由方式进行传参和接收(此处需使用 useNavigate 和 useParams 两个hooks) 1 首先需要配置好路由形式如下 :id(参数) { path: "/articleDetail/:id", element: lazyElement(<ArticleDetail />), }, 2 传递参数 使用 useNaviga…...
南京观海微电子-----PCB设计怎样降低EMI
开关模式电源是AC-DC或DC-DC电源的通用术语,这些电源使用具有快速开关动作的电路进行电压转换/转换(降压或升压)。随着每天开发出更多的设备(潜在的EMI受害者),克服EMI成为工程师面临的主要挑战,并且实现电磁兼容性(EMC)与使设备正常运行同等…...
黑苹果/Mac如何升级 Mac 新系统 Sequoia Beta 版
Mac升级教程 有必要提醒一下大家,开发者测试版系统一般是给开发者测试用的,可能存在功能不完善、部分软件不兼容的情况,所以不建议普通用户升级,如果实在忍不住,升级之前记得做好备份。 升级方法很简单: …...
2024年主流工单系统横向对比
一:智齿科技 智齿客服App可以接收工单、查看工单、分配工单、处理工单,客户问题随时随地快速解决。 与云客户中心实时连接,客户以往的浏览轨迹、聊天信息、通话记录、工单历史一目了然。 配合智齿云呼叫中心/机器人客服/人工在线客服&…...
实用软件下载:Studio One最新安装包及详细安装教程
Studio One 6是一款功能丰富、专业级的音乐制作软件,它具备灵活的工作流程和高效的团队协作能力,能帮助用户实现高质量的音乐创作和制作。 智能模板更快的启动,全新的智能模板为你手头的任务提供了必要的工具集,包括基本录制、混音…...
网络安全练气篇——常见服务端口对应漏洞
常见的端口所对应的已知漏洞 21 FTP服务的数据传输端口 22 FTP服务的连接端口,可能存在 弱口令暴力破解 389 LDAP目录访问协议,有可能存在注入、弱口令 443 HTTPS端口,心脏滴血等与SSL有关的漏洞 445 SMB服务端口,可能存…...
WPF学习(3)--不同类通过接口实现同种方法
一、接口概述 1.接口的概念 在C#中,接口(interface)是一种引用类型,它定义了一组方法、属性、事件或索引器,但不提供实现。接口只定义成员的签名,而具体的实现由实现接口的类或结构体提供。接口使用关键字…...
体验版小程序访问不到后端接口请求失败问题解决方案
文章目录 解决方案一:配置合法域名解决方案二:开发调试模式第一步:进入开发调试模式第二步:启用开发调试 注意事项结语 🎉欢迎来到Java面试技巧专栏~探索Java中的静态变量与实例变量 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&…...
【Linux文件篇】磁盘到用户空间:Linux文件系统架构全景
W...Y的主页 😊 代码仓库分享 💕 前言:我们前面的博客中一直提到的是被进程打开的文件,而系统中不仅仅只有被打开的文件还有很多没被打开的文件。如果没有被打开,那么文件是在哪里进行保存的呢?那我们又如何快速定位…...
数据分析-Excel基础函数的使用
Excel基础函数: sum:求和 sumif:单条件求和 sumifs:多条件求和 subtotal:根据筛选求和 if:逻辑判断 vlookup:连接匹配数据 match:查找数值在区域中的位置 index:根据区域的位置返回数值 match、index:一起使用:自动根据列名查找数据 sumifs、match、ind…...
速盾的防护策略有哪些?
在当今数字化时代,网络安全至关重要,而速盾作为一款优秀的安全防护工具,拥有一系列全面且有效的防护策略。 首先,速盾采用了先进的访问控制策略。通过严格的身份验证和授权机制,确保只有合法的用户和应用程序能够访问特…...
LabVIEW RT在非NI硬件上的应用与分析
LabVIEW RT(实时操作系统)可运行在非NI(National Instruments)硬件上,如研华工控机,但需要满足特定硬件要求。本文从硬件要求、开发和运行差异、可靠性、稳定性、优势和成本等多角度详细分析在非NI硬件上运…...
使用Python批量处理Excel的内容
正文共:1500 字 10 图,预估阅读时间:1 分钟 在前面的文章中(如何使用Python提取Excel中固定单元格的内容),我们介绍了如何安装Python环境和PyCharm工具,还利用搭好的环境简单测试了一下ChatGPT提…...
k8s+pv+pvc+nas 数据持久化volumes使用
1 k8s pod申请持久化卷配置 apiVersion: v1 kind: Service metadata:name: $IMG_NAMEnamespace: rz-dtlabels:app: $IMG_NAME spec:type: NodePortports:- port: 8091nodePort: 31082 #service对外开放端口selector:app: $IMG_NAME --- apiVersion: apps/v1 kind: Deployment …...
C++算法-青蛙跳台阶【面试】
"青蛙跳台阶"问题是一个经典的递归问题,也与斐波那契数列有关。问题是这样的:一只青蛙站在一个n阶台阶上,它每次可以跳1阶或2阶,问青蛙跳到顶端总共有多少种跳法。 这个问题可以用递归或动态规划来解决。以下是使用C实…...
px转rem插件postcss-plugin-px2rem使用方法(浏览器缩放页面自适应)
px转rem插件postcss-plugin-px2rem使用方法(浏览器缩放页面自适应) 1. 常见屏幕自适应的布局 百分比布局rem布局css媒体查询在前端框架设计初期,应优先选择好页面布局方式 2. postcss-plugin-px2rem插件的使用 官网地址:https…...
wordpress 咚门下载/seo做的比较牛的公司
ifconfig eth0 |grep "inet addr:" |awk {print $2}|cut -c 6- 或者ifconfig | grep inet addr:| grep -v 127.0.0.1 | cut -d: -f2 | awk { print $1} 本文转自 技术花妞妞 51CTO博客,原文链接:http://blog.51cto.com/xiaogongju/1951804...
网站建设及代运营合同/营销做得好的品牌
xp下由于自己把控制面板--区域和语言选项--区域选项中的“中文”改为了“英文/美国”,所以以中文名字命名的.chm文件就打开了,改为英文名字后正常打开参考:解决XP不能打开CHM文件2006-07-27 13:50如果你的Windows XP不能打开chm格式的文件&am…...
杭州网站排名优化/市场营销计划
现代数字系统设计——在线考试复习资料2021版 一、单选题 1. SOPC的中文意思是( )。 A.电子设计自动化 B.硬件描述语言 C.片上可编程系统 D.片上系统 答案:看左边查询 2.XPS是Xilinx公司提供的( )。 A.嵌入式系统的集成硬件设计和仿真工具 B.编辑FPGA的I/O引脚和面积约…...
max age 0 wordpress/网站推广在线
ssh 公钥 和私钥配好后,在idea的terminal窗口仍然需要每次输入用户名和密码。 用命令 git add remote origin master gitgithub.com:zk/first-githup.git 即可 还可以解决 fatal: Not a git repository (or any of the parent directories): .git 问题...
游戏网站代码/超级软文网
■set 置数; ■unset 这个命令从解释器中删除变量,它后面可以有任意多个参数,每个参数是一个变量名,可以是简单变量,也可以是数组或数组元素。例如: % unset a b day(monday) 上面的语句中删除了变量 a、 b 和数组元素…...
网站建设 南宁/网络推广100种方式
20172303 2017-2018-2 《程序设计与数据结构》第9周学习总结 教材学习内容总结 第十一章 异常 1.异常 异常处理 异常处理的常用方法有三种: 根本不处理当异常发生时处理异常在程序的某个位置集中处理异常处理异常的主要方法是捕获异常异常捕获——try-catch语句 格式…...