当前位置: 首页 > news >正文

「JavaEE」多线程案例1:单例模式阻塞队列

🎇个人主页:Ice_Sugar_7
🎇所属专栏:JavaEE
🎇欢迎点赞收藏加关注哦!

多线程案例分析

  • 🍉单例模式
    • 🍌饿汉模式
    • 🍌懒汉模式
    • 🍌指令重排序
  • 🍉阻塞队列
    • 🍌生产者消费者模型
    • 🍌实现阻塞队列

🍉单例模式

单例模式是一种设计模式。所谓“单例”,就是只有一个实例
如果某个类在一个进程中只应该创建出一个实例(或者说原则上不应该有多个),那么使用单例模式就可以对我们的代码进行更严格的校验和检查
要严格控制实例的数量是因为有时候我们需要用一个对象来管理大量数据,比如一个对象管理 10G 数据,如果不小心创建多个对象,那么占用的内存空间就会成倍增长,这就会带来很大的开销

有很多种方式来实现单例模式,本文介绍两种基础的实现方式——饿汉模式懒汉模式

🍌饿汉模式

public class Singleton {private static Singleton instance = new Singleton(); //这个引用就是我们期望创建出的唯一实例的引用,加 static 保证唯一性public static Singleton getInstance() { //其他类如果想使用这个类的实例,必须通过这个方法获取现成的实例return instance;}private Singleton() {} //为了防止在类外 new 一个 Singleton对象,用 private 把构造方法封装起来
}

上面的代码就称为饿汉模式,实例在类加载的时候就创建了,相当于程序一启动,实例就创建了,创建时机非常早。而“饿”字形容“非常迫切的样子”,所以就用饿汉来描述很早就创建实例这种行为

🍌懒汉模式

这种模式创建实例的时机和饿汉模式不一样。它创建时机会更晚,只有第一次使用的时候才会创建实例
我们来看下具体如何实现:

public class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if(instance == null)instance = new SingletonLazy(); //如果是首次调用这个方法,就会创建一个实例;如果后续再次调用 getInstance,就会返回之前创建好的引用return instance;}private SingletonLazy() {} //同样是防止在类外创建实例
}

接下来我们来分析一下上述两种模式是否是线程安全的,其实也就是分析在多线程中并发调用 getInstance 是否线程安全

对于饿汉模式来说,getInstance 直接返回 instance 实例,这个操作本质上是“读”操作,多个线程读取同一个变量,肯定是线程安全的

而懒汉模式的 getInstance 涉及到读和写
那么考虑下面这个场景:

  1. t1 第一次调用 getInstance,在执行完 if 语句后被调度走,轮到 t2 来执行
  2. 那此时 t2 就会创建一个新实例,并把它的引用给到 instance,instance 就不为空了
  3. 当 t2 执行完轮到 t1,t1 又会 new 一个实例,这就 new 了两次实例!!!不再是单例模式

在这里插入图片描述

所以懒汉模式不是线程安全的,我们需要使用 synchronized 加锁来改进懒汉模式
我们需要把 if 语句和 new 实例打包成一个原子

synchronized (locker) {if (instance == null)instance = new SingletonLazy(); //若为空,则创建一个实例
}

不过这样有一个问题,就是如果一个线程已经创建好实例了,后续其他线程每次调用还要拿到锁之后再进来判断 instance 是否为空,但显然都不为空,所以就做了无用功,而且加锁解锁会导致效率非常低
所以需要再在 synchronized 外面套一个条件语句判断是否需要加锁

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

这里巧合的是两个 if 的条件是一样的,不过它们的目的不同。既保证了线程安全,又保证执行效率,这样的锁称为双重校验锁
在多线程中,上面这样的代码是很有意义的,看起来是两个一样的条件,但实际上这两个条件的结果可能是相反的

不过尽管如此,上面的代码还是有一些问题,就是指令重排序引起的线程安全问题

🍌指令重排序

这也是编译器的一种优化方式,编译器会在保证逻辑不变的前提下,调整原有代码的执行顺序,提高程序效率

instance = new SingletonLazy();

上面这行代码,其实可以拆分为三个大的步骤

  1. 申请一段内存空间
  2. 在内存中调用构造方法,创建出实例
  3. 把这个内存地址赋值给 instance

正常情况下是按照 1 2 3 的顺序执行的,但是编译器可能会优化为 1 3 2 的顺序
先执行 1 再执行 3 的话,instance 虽然不为 null,但是它指向的是尚未初始化的对象
这两种顺序在单线程下都是可以的,但是多线程下就有问题。接下来我们按照 1 3 2 的顺序演示一下

在这里插入图片描述

我们把 new 拆分成 3 步,如果 t1 执行完 3 后被调度走,轮到 t2 执行,那么会直接跳转到 return,但此时 instance 为空, 这就会导致 t2 使用的是一个未初始化的对象,这就可能会出现错误!(因为你可能在构造方法中给实例赋值)

要解决指令重排序问题,还是得用到我们之前提到的 volatile
只需在 instance 前面加上它就 ok 了

private volatile static SingletonLazy instance = null;

总结一下 volatile 的功能

① 保证内存可见性,让每次访问变量都必须重新读取内存,而非使用寄存器中缓存的值
② 禁止指令重排序,被 volatile 修饰的变量,它读写操作相关的指令不能被重排序

下面摆出整个代码,注意思考注释中的问题(面试常考)

public class SingletonLazy {private volatile static SingletonLazy instance = null; //3.这里加 volatile 有什么用private static Object locker = new Object();public static SingletonLazy getInstance() {if (instance == null) { //2.这里为啥要判断 instance 是否为空synchronized (locker) { //1.这里为啥要加锁if (instance == null)instance = new SingletonLazy();}}return instance;}private SingletonLazy() {}
}

🍉阻塞队列

阻塞队列是在普通队列的基础上进行了拓展。它有以下两个特点:

  1. 线程安全
  2. 具有阻塞特性

入队列时,如果队列已经满了,那此时入队列操作就会阻塞,一直阻塞到队列不满的时候(其他线程出队列元素)
出队列时,如果队列为空,那么出队列操作也会阻塞,一直阻塞到队列不为空(其他线程入队列元素)


🍌生产者消费者模型

基于阻塞队列,可以实现生产者消费者模型

以生活中包饺子为例,有一个人负责擀饺子皮,另一个人包饺子
擀饺皮的人称为生产者,因为他擀完一个饺皮后饺子皮数目+1;与之相对,包饺子的人就是消费者
假设擀饺皮擀得很快,那么包饺子的人就会跟不上,这就会导致桌上的饺皮越来越多,直到满了,此时生产饺皮的人就要停下来等一会儿,等饺子皮少一些之后再继续生产
同理,如果包饺子的人包得很快,就会导致桌上没有饺皮了,那么他就得等擀出一些饺子皮后再包饺子

在这里插入图片描述
在上面的例子中,我们会发现桌子起到传递饺子皮的作用,它就相当于阻塞队列

生产者消费者模型在实际开发中是非常有意义的

  1. 引入这个模型可以更好地做到解耦合

所谓解耦合就是降低代码的耦合程度
在实际开发中,服务器的所有功能不只由一个服务器完成,而是每个服务器负责其中一部分功能,然后通过服务器之间的网络通信完成整个功能
以电商平台为例,服务器之间是这样处理请求的:

在这里插入图片描述

A 和处理请求的服务器 B、C、D 之间不是直接进行交互,而是通过队列传递请求。这样,如果 B、C 挂了,对 A 的影响其实是微乎其微的,而且如果后续再添加其他服务器,A的代码也几乎不用变化

  1. 削峰填谷

所谓“削峰”,就是当外界的请求突然大量增加的时候,让阻塞队列来存放这些请求,B、C 仍然按照之前的速度来取请求,这样就保证 B 和 C 不会因为请求骤增然后挂了
(一般 B 和 C 这些服务器的抗压能力比 A 的弱很多,不小心就会寄了)

而“填谷”则是指在外界请求突然减少的时候,由于阻塞队列之前已经存了一些请求,所以它仍然可以按照原先的速率发放请求给 B、C 等服务器

这两个场景都说明阻塞队列具有缓冲作用


🍌实现阻塞队列

分为三步来实现

  1. 先实现一个普通队列
  2. 再考虑线程安全问题
  3. 再加上阻塞功能。有阻塞就有 notify,因入队列而阻塞的线程,当队列不满的时候就应该解除阻塞,所以要在出队列操作中加入 notify;同理入队列操作中也要有 notify

队列的话可以用一个数组来实现,用两个“指针”分别指向队首和队尾元素,同时用一个变量 size 标记当前队列有多少元素

public class MyBlockingQueue {String[] queue;int head,tail; //队首和队尾int size; //当前队列元素个数Object locker = new Object();MyBlockingQueue(int capacity) {queue = new String[capacity];}public void put(String str) throws InterruptedException {synchronized (locker) {if (size >= queue.length) { //注意“判断队列是否满了”这一步也要加锁(即放在 synchronized 里面)locker.wait();}queue[tail++] = str;if (tail >= queue.length) tail = 0; //也可以不用判断,直接写成 tail %= queue.length;  不过这样的效率会低一些size++; //不要忘了让 size++locker.notify(); //唤醒一个“因出队列时队列为空而阻塞”的线程}}public String take() throws InterruptedException { //出队并返回该元素String ret = null;synchronized (locker) {if (size == 0) {locker.wait();}ret = queue[head];head++;if (head == queue.length) head = 0;size--;locker.notify(); //唤醒一个“因入队列时队列满了而阻塞”的线程}return ret;}
}

不过上面代码出入队列的操作还是有问题

拿入队列来说,如果队列已经满了,两个线程同时执行 put,那么它们都会阻塞。当出队列唤醒其中一个线程后,它继续执行 put,执行到最后会 notify,因为锁对象只有一个,所以此时另外一个线程就有可能因此被唤醒。而队列已经满了,再 put 一次就会出问题了
(同理出队列也是这样分析的)

解决办法就是把判断队列为空/为满的 if 语句改成 while 循环,因为 if 语句只会判断一次,而 while 循环可以多次判断

在上面的情况中,当另一个线程被唤醒之后,会先判断队列是否满了,显然此时已经满了,那么它就会再次进入阻塞状态

while (size >= queue.length) {locker.wait();
}while (size == 0) {locker.wait();
}

值得一提的是,Java 标准库中也推荐 wait 和 while 配套使用

相关文章:

「JavaEE」多线程案例1:单例模式阻塞队列

🎇个人主页:Ice_Sugar_7 🎇所属专栏:JavaEE 🎇欢迎点赞收藏加关注哦! 多线程案例分析 🍉单例模式🍌饿汉模式🍌懒汉模式🍌指令重排序 🍉阻塞队列&a…...

pdf2htmlEX:pdf 转 html,医学指南精细化处理第一步

pdf2htmlEX:pdf 转 html,医学指南精细化处理第一步 单文件转换多文件转换 代码:https://github.com/coolwanglu/pdf2htmlEX 拉取pdf2htmlEX 的 Docker: docker pull bwits/pdf2htmlex # 拉取 bwits/pdf2htmlex不用进入容器&…...

【webrtc】MessageHandler 6: 基于线程的消息处理:StunRequest实现包发送和超时重传

G:\CDN\rtcCli\m98\src\p2p\base\stun_request.cc使用OnMessage 实现包的发送和包的超时重传StunRequest 一个StunRequest 代表是一个独立的请求的发送STUN消息 要不是发送前构造好的,要不就是按照需要构建的使用StunRequestManager: 每一个STUNRequest 携带一个交互id 写入m…...

《Python编程从入门到实践》day22

# 昨日知识点回顾 方法重构、驾驶飞船左右移动、全屏显示 飞船不移动解决,问题出在移动变量x更新 # Ship.pysnipdef update(self):"""根据移动标志调整飞船的位置"""# 更新飞船而不是rect对象的x值# 如果飞船右移的标志和飞船外接…...

介绍 ffmpeg.dll 文件以及ffmpeg.dll丢失怎么办的五种修复方法

ffmpeg.dll 是一个动态链接库文件,属于 FFmpeg运行库。它在计算机上扮演着非常重要的角色,因为它提供了许多应用程序和操作系统所需的功能和组件。当 ffmpeg.dll 文件丢失或损坏时,可能会导致程序无法正常运行,甚至系统崩溃。下面…...

AI换脸原理(6)——人脸分割介绍

一、介绍 人脸分割是计算机视觉和图像处理领域的一项重要任务,它主要涉及到将图像中的人脸区域从背景或其他非人脸区域中分离出来。这一技术具有广泛的应用场景,如人脸识别、图像编辑、虚拟背景替换等。 在计算机视觉(CV)领域,经典的分割技术可以主要划分为三类:语义分…...

【C++并发编程】(二)线程的创建、分离和连接

文章目录 (二)线程的创建、分离和链接创建线程:示例线程的分离(detach)和连接(join) (二)线程的创建、分离和链接 创建线程:示例 线程(Thread&a…...

利用生成式AI重新构想ITSM的未来

对注入 AI 的生成式 ITSM 的需求,在 2023 年 Gartner AI 炒作周期中,生成式 AI 达到预期值达到顶峰后,三分之二的企业已经将生成式 AI 集成到其流程中。 你问为什么这种追求?在预定义算法的驱动下,IT 服务交付和管理中…...

完美解决AttributeError: module ‘backend_interagg‘ has no attribute ‘FigureCanvas‘

遇到这种错误通常是因为matplotlib的后端配置问题。在某些环境中,尤其是在某些特定的IDE或Jupyter Notebook环境中,可能会因为后端配置不正确而导致错误。错误信息提示 module backend_interagg has no attribute FigureCanvas 意味着当前matplotlib的后…...

CMakeLists.txt语法规则:条件判断中表达式说明一

一. 简介 前面学习了 CMakeLists.txt语法中的 部分常用命令,常量变量,双引号的使用。 前面一篇文章也简单了解了 CMakeLists.txt语法中的条件判断,文章如下: CMakeLists.txt语法规则:条件判断说明一-CSDN博客 本文…...

《QT实用小工具·五十三》会跑走的按钮

1、概述 源码放在文章末尾 该项目实现了会逃跑的按钮: 两个按钮,一个为普通按钮,另一个为会跑走的按钮 鼠标移到上面时,立刻跑掉 针对鼠标、键盘、触屏进行优化 随机交换两个按钮的文字、偶尔钻到另一个按钮下面、鼠标移开自…...

Servlet的几种用法?

serlet 1.定义&#xff1a;Serlet是使用Java编写的运行在服务器端的程序 2.Servlet主要是用于处理浏览器端发送的Http请求&#xff0c;并返回一个响应 3.Servlet开发需要使用到的包&#xff1a; java.servlet java.servlet.http 一.Servlet注册 1.xml方式 <servlet>…...

Golang | Leetcode Golang题解之第69题x的平方根

题目&#xff1a; 题解&#xff1a; func mySqrt(x int) int {if x 0 {return 0}C, x0 : float64(x), float64(x)for {xi : 0.5 * (x0 C/x0)if math.Abs(x0 - xi) < 1e-7 {break}x0 xi}return int(x0) }...

AR人脸美妆SDK解决方案,让妆容更加贴合个人风格

美妆行业正迎来前所未有的变革&#xff0c;为满足企业对高效、精准、创新的美妆技术需求&#xff0c;美摄科技倾力打造了一款企业级AR人脸美妆SDK解决方案&#xff0c;为企业打开美妆领域的新世界大门。 革命性的人脸美妆技术 美摄科技的AR人脸美妆SDK解决方案&#xff0c;不…...

Python-100-Days: Day09 Object-oriented programming(OOP) Upgrade

1.property装饰器 之前有讨论过&#xff0c; Python中属性和方法访问权限的问题&#xff0c;不建议将属性设置为私有的&#xff0c;倘若直接将属性暴露给外界也是存在问题的。例如&#xff0c;我们没有办法检查赋给属性的值是否有效。之前的建议是将属性命名以单下划线开头&am…...

虹科Pico汽车示波器 | 免拆诊断案例 | 2010款凯迪拉克SRX车发动机无法起动

故障现象 一辆2010款凯迪拉克SRX车&#xff0c;搭载LF1发动机&#xff0c;累计行驶里程约为14.3万km。该车因正时链条断裂导致气门顶弯&#xff0c;大修发动机后试车&#xff0c;起动机运转有力&#xff0c;但发动机没有着机迹象&#xff1b;多起动几次&#xff0c;火花塞会变…...

ECC 号码总结

1、问题背景 在手机开发过程中&#xff0c;经常遇见各种紧急号码问题&#xff0c;在此特意总结下紧急号码相关知识。 2、紧急号码来源 在MTK RILD EccNumberSource.h中&#xff0c;定义了如下几种紧急号码来源。 按优先级排序介绍如下 2.1、SOURCE_NETWORK 网络下发&#xff…...

《大疆二次开发》EMQX和MQTT部署

EMQX 服务器 基础知识 概念 EMQX (Erlang/Enterprise/Elastic MQTT Broker) &#xff1b;EMQ/EMQX就是MQTT Broker的一种实现&#xff1b;一款开源的大规模分布式 MQTT 消息服务器&#xff0c;功能丰富&#xff0c;专为物联网和实时通信应用而设计&#xff1b;支持多种协议&…...

【网络】滑动窗口和拥塞窗口

滑动窗口和拥塞窗口是TCP协议中两个重要的窗口概念&#xff0c;它们分别用于流量控制和拥塞控制&#xff0c;在功能和作用上有所不同。 滑动窗口&#xff08;Sliding Window&#xff09; 滑动窗口是用于流量控制的机制&#xff0c;它定义了发送方和接收方之间的数据传输量。T…...

数据库知识初步汇总

创建标签表格&#xff1a; CREATE TABLE IF NOT EXISTS labels (标签ID INTEGER PRIMARY KEY,标签名称 TEXT );创建文本与标签的关联表格&#xff1a; CREATE TABLE IF NOT EXISTS 文本标签 (文本ID INTEGER,标签ID INTEGER,FOREIGN KEY (文本ID) REFERENCES texts(编号),FOR…...

Moby简介:openEuler 中的开源docker引擎

Moby 是一个开源的容器化引擎&#xff0c;它提供了创建和管理容器所需的核心功能。在 openEuler 系统中&#xff0c;Moby 作为容器技术的实现之一&#xff0c;它允许用户利用容器化技术来部署、运行和移植应用程序。 Moby 的功能和作用&#xff1a; 1. **容器创建**&#xff…...

分布式光纤测温DTS的测温范围是多少?

分布式光纤测温DTS的测温范围不仅仅取决于光缆的感温能力&#xff0c;还受到多种复杂因素的影响。尽管高温光缆可以耐高温&#xff0c;低温光缆可以耐低温&#xff0c;甚至镀金光缆能够耐受高达700摄氏度的极高温度&#xff0c;然而&#xff0c;这些因素并不能完全解释测温范围…...

Java实现裁剪PDF

目录 安装Java PDF库 Java裁剪PDF页面 Java裁剪PDF页面并将结果保存为图片、HTML、Excel等格式 裁剪PDF页面是一项常见的任务&#xff0c;它可以用来调整文档的尺寸和去除不需要的边距或白边。通过裁剪页面&#xff0c;你可以优化文档的布局和展示效果&#xff0c;使其更符合…...

ZooKeeper以及DolphinScheduler的用法

目录 一、ZooKeeper的介绍 数据模型 ​编辑 操作使用 ①登录客户端 ​编辑 ②可以查看下面节点有哪些 ③创建新的节点&#xff0c;并指定数据 ④查看节点内的数据 ⑤、删除节点及数据 特殊点&#xff1a; 运行机制&#xff1a; 二、DolphinScheduler的介绍 架构&#…...

gitlab集群高可用架构拆分部署

目录 前言 负载均衡器准备 外部负载均衡器 内部负载均衡器 (可选)Consul服务 Postgresql拆分 1.准备postgresql集群 手动安装postgresql插件 2./etc/gitlab/gitlab.rb配置 3.生效配置文件 Redis拆分 1./etc/gitlab/gitlab.rb配置 2.生效配置文件 Gitaly拆分 1.…...

STC8增强型单片机开发day01

C51版本Keil环境搭建 搭建流程 环境搭建的基本流程&#xff1a; 从官方网站下载并安装Keil软件。选择安装的软件中的C51工具集并运行。通过从“文件”菜单中选择“项目”来创建新项目。输入项目名称并选择您正在使用的设备。通过从“项目”菜单中选择“添加文件到组”来添加…...

记录: Python解析yml文件,顺序解析,带所有文件等号

记录: Python解析yml文件&#xff0c;顺序解析&#xff0c;带所有文件等号from yaml.composer import Composer from yaml.constructor import Constructor import yamlclass ParseYml:def __init__(self):passstaticmethoddef parse(yml_pathNone):try:loader yaml.Loader(op…...

Npm Install Docusaurus Demo【npm 安装 docusaurus 实践 】

文章目录 1. 简介2. 前提2.1 安装 git2.2 安装 node 3. 安装4. 项目结构5. 访问5.1 localhost 访问5.2 ip 访问 1. 简介 Docusaurus 是一个facebook的开源项目&#xff0c;旨在帮助开发者构建易于维护和部署的文档网站。它提供了一个简单的方法来创建专业的文档网站&#xff0…...

【工具推荐定制开发】一款轻量的批量web请求命令行工具支持全平台:hey,基本安装、配置、使用

背景 在开发 Web 应用的过程中&#xff0c;作为开发人员&#xff0c;为了确认接口的性能能够达到要求&#xff0c;我们往往需要一个接口压测工具&#xff0c;帮助我们快速地对我们所提供的 Web 服务发起批量请求。在接口联调的过程中&#xff0c;我们通常会用 Postman 等图形化…...

Linux进程——进程的创建(fork的原理)

前言&#xff1a;在上一篇文章中&#xff0c;我们已经会使用getpid/getppid函数来查看pid和ppid,本篇文章会介绍第二种查看进程的方法&#xff0c;以及如何创建子进程&#xff01; 本篇主要内容&#xff1a; 查看进程的第二种方法创建子进程系统调用函数fork 在开始前&#xff…...

做动画视频的网站/去哪里找需要推广的app

博客转载自&#xff1a;http://www.cnblogs.com/ironstark/p/4991232.html 点云滤波的概念 点云滤波是点云处理的基本步骤&#xff0c;也是进行 high level 三维图像处理之前必须要进行的预处理。其作用类似于信号处理中的滤波&#xff0c;但实现手段却和信号处理不一样。我认为…...

网站制作优化济南/长春网站优化流程

写在前面 笔记内容大多出自于拉勾教育大前端高薪训练营的教程&#xff0c;因此也许会和其他文章雷同较多&#xff0c;请担待。 Yeoman $ npm i yo -g / $ yarn global add yo $ npm i generator-node -g / $ yarn global add generator-node // 基于node的开发脚手架&#x…...

wordpress 远程管理/2023引流软件

php 图片局部打马赛克 原理&#xff1a; 对图片中选定区域的每一像素&#xff0c;添加若干宽度及高度&#xff0c;生成矩型。而每一像素的矩型重叠在一起。就形成了马赛克效果。本例使用GD库的imagecolorat获取像素颜色&#xff0c;使用imagefilledrectangle画矩型。效果图&…...

wordpress 顶栏显示/百度注册页面

问题 问题1&#xff1a; 最近有不少用户反馈使用 php runtime的时候遇见如下报错Cannot modify header information - headers already sent by (output started at ... 问题2&#xff1a; 如果更改php 的session 目录&#xff1f;本文旨在梳理此类问题的原因&#xff0c;触发条…...

自由贸易试验区网站建设方案/短视频获客系统

1、HOG特征&#xff1a; 方向梯度直方图&#xff08;Histogram of Oriented Gradient, HOG&#xff09;特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向直方图来构成特征。Hog特征结合SVM分类器已经被广泛应用于图像…...

自己做网站教程/seo的形式有哪些

1.send 函数 int send( SOCKET s, const char FAR *buf, int len, int flags ); 不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求&#xff0c;而服务器则通常用send函数来向客户程序发送应答。 该函数的第一…...