JavaEE-经典多线程样例

文章目录

  • 单例模式
    • 设计模式初步引入
    • 为何存在单例模式
    • 饿汉式单例模式
    • 饿汉式缺陷以及是否线程安全
    • 懒汉式单例模式
    • 基础懒汉式缺陷以及是否线程安全
    • 懒汉式单例模式的改进
    • 完整代码(变量volatile)
  • 阻塞队列
    • 生产者消费者模型
    • 生产者消费者模型的案例以及优点
      • 请求与响应案例
      • 解耦合

单例模式

设计模式初步引入

啥是设计模式?

  • 设计模式好⽐象棋中的 “棋谱”. 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀些固定的套路. 按照套路来⾛局势就不会吃亏.软件开发中也有很多常⻅的 “问题场景”. 针对这些问题场景, ⼤佬们总结出了⼀些固定的套路. 按照这个套路来实现代码, 也不会吃亏, 不针对某一种语言, 而是针对某种开发场景
  • 设计模式并不是只有23种, 因为之前有些大佬写了一本书叫设计模式,重点讨论了23种, 但事实上存在更多种的设计模式
  • 设计模式与框架的区别就是, 设计模式在开发中是软性要求(不一定遵守), 但是框架是硬性要求(一定要遵守)

简单点一句话总结

设计模式是前人根据一些开发场景给出的一些经验之谈, 所以设计模式并不针对某一种语言

为何存在单例模式

  • 单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例.
    这⼀点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要⼀个,再
    比如如果一个类的创建需要加载的数据量非常的庞大(GB级别), 那我们不希望这
    个类频繁的创建销毁(开销很大), 我们可能只是希望创建一次就可以了

饿汉式单例模式

顾名思义, 这种方式实现的单例模式十分"饥渴", 不管使用不使用都会提前new一个对象

流程如下

  • 构造方法私有化
  • 定义一个静态的类对象用以返回
  • 提供一个公开的静态接口来获取唯一的对象

测试代码如下

/*** 下面定义一个类来测试饿汉式单例模式*/
class HungrySingleton{// 提供一个静态的变量用来返回private static HungrySingleton hungrySingleton = new HungrySingleton();// 构造方法私有化(在外部不可以构造对象)private HungrySingleton(){}// 提供一个获取实例的静态公开接口public static HungrySingleton getInstance(){return hungrySingleton;}
}public class DesignPatternTest {public static void main(String[] args) {// 对饿汉式单例的测试HungrySingleton instance1 = HungrySingleton.getInstance();HungrySingleton instance2 = HungrySingleton.getInstance();// 测试两者是不是一个对象System.out.println(instance1 == instance2);}
}

测试结果
在这里插入图片描述
很明显, 用这种方式创建的实例都是只有一份的…

饿汉式缺陷以及是否线程安全

首先饿汉式的单例模式缺陷是非常明显的

  • 饿汉式不管我们使用这个对象与否, 都会在类加载的时期(因为是静态对象)构建一个这样的对象, 但我们想要达成的效果是, 在我们不需要这种类的实例的时候, 我们不去进行构造对象的操作(变主动为被动)来减少内存等相关资源的开销

但是饿汉式单例一定是线程安全的

  • 构建对象的时期是类加载的时候, 后期不同线程对于这个实例的操作也仅仅是涉及到读操作, 不涉及修改操作, 所以当然是线程安全的, 不存在线程安全问题, 但是另一种实现的模式就不一定了

懒汉式单例模式

上面说了饿汉式单例模式的缺陷, 我们尝试使用懒汉式单例的方式去解决这个问题, 也就是仅仅在需要的时候进行new对象的操作

最基础的懒汉单例模式

构造的逻辑

  • 构造方法私有化
  • 提供一个静态的对象用来返回(暂时不new对象)
  • 提供一个公开访问的静态接口来返回唯一的对象

代码测试(最基础的版本)

/*** 下面定义一个类来测试懒汉式单例模式*/
class LazySingleton{// 提供一个静态的变量用来返回private static LazySingleton lazySingleton = null;// 构造方法私有化(不可以在外部new对象)private LazySingleton(){}// 提供一个公开的获取实例的接口public static LazySingleton getInstance(){if(lazySingleton == null){lazySingleton = new LazySingleton();}return lazySingleton;}
}public class DesignPatternTest {public static void main(String[] args) {// 对懒汉式单例的测试LazySingleton instance1 = LazySingleton.getInstance();LazySingleton instance2 = LazySingleton.getInstance();// 测试两者是不是一个对象System.out.println(instance1 == instance2);}
}

基础懒汉式缺陷以及是否线程安全

这个就和上面饿汉有较大的区别了, 虽然解决了在需要的时候进行new对象, 上面的基础版本的懒汉式在单线程的环境下肯定是没问题的, 但是在多线程的环境下就不好说了…看下面的分析

如果在多线程的环境下(我们假设有t1, t2)是下图的执行顺序
在这里插入图片描述
很明显这是一种类似串行的执行策略

但是还可能是下图的情况
在这里插入图片描述
t1线程判断完毕之后没有来得及进行new对象, t2线程紧接着进行了一次完整的new对象的过程, 此时t1线程又进行了一次new对象的过程, 很明显, 我们上面的情况进行了两次构造对象的过程, 同时拿到的对象也不一致

我们通过Thread.sleep()的方式进行延迟观察看是否会发生

/*** 下面定义一个类来测试懒汉式单例模式*/
class LazySingleton{// 提供一个静态的变量用来返回private static LazySingleton lazySingleton = null;// 构造方法私有化(不可以在外部new对象)private LazySingleton(){}// 提供一个公开的获取实例的接口public static LazySingleton getInstance() throws InterruptedException {if(lazySingleton == null){Thread.sleep(1000);lazySingleton = new LazySingleton();}return lazySingleton;}
}public class DesignPatternTest {private static LazySingleton instance1 = null;private static LazySingleton instance2 = null;public static void main(String[] args) throws InterruptedException {// 创建两个线程获取实例Thread t1 = new Thread(() -> {try {instance1 = LazySingleton.getInstance();} catch (InterruptedException e) {e.printStackTrace();}});Thread t2 = new Thread(() -> {try {instance2 = LazySingleton.getInstance();} catch (InterruptedException e) {e.printStackTrace();}});// 开启两个线程t1.start();t2.start();// 睡眠等待一下Thread.sleep(2000);System.out.println(instance1 == instance2);}
}

在这里插入图片描述
很明显, 这样的懒汉式的代码是线程不安全的, 那要如何进行改进呢???

懒汉式单例模式的改进

之前我们说了, 要想保证线程是安全的, 有几种解决方式, 这里面我们就采取加锁, 因为其实
判断是不是null和对象应该是一个整体的原子性的操作

改进之后的代码

/*** 下面定义一个类来测试懒汉式单例模式(改进版)*/
class LazySingleton{// 提供一个静态的变量用来返回private static LazySingleton lazySingleton = null;// 构造方法私有化(不可以在外部new对象)private LazySingleton(){}// 提供一个公开的获取实例的接口public static LazySingleton getInstance() {// 我们把if判断和new对象通过加锁打包为一个原子性的操作(这里使用类对象锁)synchronized (LazySingleton.class){if(lazySingleton == null){lazySingleton = new LazySingleton();}}return lazySingleton;}
}public class DesignPatternTest {private static LazySingleton instance1 = null;private static LazySingleton instance2 = null;public static void main(String[] args) {// 在多线程中获取实例Thread t1 = new Thread(() -> {instance1 = LazySingleton.getInstance();});Thread t2 = new Thread(() -> {instance2 = LazySingleton.getInstance();});System.out.println(instance1 == instance2);}
}

这时候肯定是一个线程安全的代码了, 但是思考可不可以进一步改进呢???


当我们已经new个一次对象之后, 如果后续的线程想要获取这个对象, 那就仅仅是一个操作了, 根本不涉及对对象的修改, 但是我们每次都使用锁这样的机制就会造成阻塞, 也就会导致程序的效率下降, 所以我们对代码进行了下面的修改在外层再加一个if判断

改进的方法如下

// 提供一个公开的获取实例的接口public static LazySingleton getInstance() {// 我们把if判断和new对象通过加锁打包为一个原子性的操作(这里使用类对象锁)if (lazySingleton == null) {synchronized (LazySingleton.class) {if (lazySingleton == null) {lazySingleton = new LazySingleton();}}}return lazySingleton;}

我们的两个if的含义

  • 第一个if: 判断对象是否创建完毕, 如果创建了, 只是一个读操作
  • 第二个if: 判断是不是需要new对象

可能初学多线程的时候, 看上述代码觉得很迷惑, 但其实这是因为之前我们写的程序都是单线程的情况, 单线程中执行流只有一个, 两次相同的if判断其实是没有必要的, 但是多线程的条件下, 是多个执行流, 相同的逻辑判断条件也可能产生不同的结果

完整代码(变量volatile)

关于变量是否会产生指令重排序和内存可见性问题, 我们直接加上volatile即可

/*** 下面定义一个类来测试懒汉式单例模式(完整改进版)*/
class LazySingleton {// 提供一个静态的变量用来返回private volatile static LazySingleton lazySingleton = null;// 构造方法私有化(不可以在外部new对象)private LazySingleton() {}// 提供一个公开的获取实例的接口public static LazySingleton getInstance() {// 我们把if判断和new对象通过加锁打包为一个原子性的操作(这里使用类对象锁)if (lazySingleton == null) {synchronized (LazySingleton.class) {if (lazySingleton == null) {lazySingleton = new LazySingleton();}}}return lazySingleton;}
}

阻塞队列

生产者消费者模型

关于生产者消费者模型, 其实是生活中抽象出来的一个模型案例, 我们举一个包饺子的例子来简单解释一下

  • 在包饺子的过程中, 存在一个擀饺子皮的人, 我们称之为生产者, 擀出来的饺子皮放到一个竹盘上, 这个竹盘相当于一个中间的媒介, 生产者生产的物质在上面与消费者进行交互, 而包饺子的人就是一个消费者, 从中间媒介中取出东西, 也就是消费的过程, 我们的中间的竹盘相当于一个缓冲, 如果包饺子的人包的快的话, 就需要等待做饺子皮的人, 如果做饺子皮的人做的快的话, 当竹盘放不下的时候就需要阻塞等待

  • 上面的情景抽象成生产者消费者模型, 擀饺子皮的人是生产者, 竹盖是阻塞队列, 包饺子的人是消费者

生产者消费者模型的案例以及优点

请求与响应案例

生产者消费者模型我们举一个"请求响应的案例"

在这里插入图片描述
图中我们也有解释, 越靠上游的消耗的资源越少
假设我们现在出现一个秒杀的请求, 上游可能还可以运行, 但是下游的服务器由于并发量过大就直接崩溃了

在这里插入图片描述
所以我们一般会对上面提供服务的逻辑进行改变
添加一个中间的结构(阻塞队列, 或者说消息队列)进行缓冲

在这里插入图片描述


在真实的开发场景当中, 阻塞队列甚至会单独的部署为一台服务器, 这种独立的服务器结构叫做消息队列, 可见其重要性


解耦合

生产者消费者模型的一个重要的优点就是让消费者和生产者解耦合

  • 根据上面的模型分析, 不管是生产者还是消费者都是面向阻塞队列来进行任务的执行的, 所以就降低了两者之间的耦合度, 将来想要修改这个模型的工作内容, 也只需要面向阻塞队列操作更改(其实相当于接口), 如果没有这种机制的话, 我们想要更改一个操作逻辑, 就需要同时修改消费者与生产者的代码结构…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/482143.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构与算法】排序算法(上)——插入排序与选择排序

文章目录 一、常见的排序算法二、插入排序2.1、直接插入排序2.2、希尔排序( 缩小增量排序 ) 三、选择排序3.1、直接选择排序3.2、堆排序3.2.1、堆排序的代码实现 一、常见的排序算法 常见排序算法中有四大排序算法,第一是插入排序,二是选择排序&#xff…

qml项目创建的区别

在Qt框架中,你可以使用不同的模板来创建应用程序。你提到的这几个项目类型主要针对的是Qt的不同模块和用户界面技术。下面我将分别解释这些项目类型的区别: 根据你提供的信息,以下是每个项目模板的详细描述和适用场景: Qt Widgets…

【热门主题】000077 物联网智能项目:开启智能未来的钥匙

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 【热…

时序约束进阶六:Set_Clock_Groups详解

目录 一、前言 二、时钟间关系 2.1 时钟关系分类 2.2 时钟关系查看 三、set_clock_groups设置 3.1 使用格式 3.2 优先级 3.3 约束设置示例 3.4 约束效果查看 四、Exclusive差异说明 4.1 Asynchronous 4.2 Logically_exclusive与Physically_exclusive 4.3 logical…

智慧银行反欺诈大数据管控平台方案(一)

智慧银行反欺诈大数据管控平台建设方案的核心在于通过整合先进的大数据技术和深度学习算法,打造一个全面、智能且实时的反欺诈系统,以有效识别、预防和应对各类金融欺诈行为。该方案涵盖数据采集、存储、处理和分析的全流程,利用多元化的数据…

系统架构:MVVM

引言 MVVM 全称 Model-View-ViewModel,是在 MVP(Model-View-Presenter)架构模式基础上的进一步演进与优化。MVVM 与 MVP 的基本架构相似,但 MVVM 独特地引入了数据双向绑定机制。这一创新机制有效解决了 MVP 模式中 Model 与 Vie…

ARM CCA机密计算安全模型之硬件强制安全

安全之安全(security)博客目录导读 [要求 R0004] Arm 强烈建议所有 CCA 实现都使用硬件强制的安全(CCA HES)。本文件其余部分假设系统启用了 CCA HES。 CCA HES 是一个可信子系统的租户——一个 CCA HES 主机(Host),见下图所示。它将以下监控安全域服务从应用处理元件(P…

【电子通识】失效分析的流程和方法

在文章:【电子通识】失效分析的基本概念-CSDN博客 中我们讲到失效分析是是指产品失效后,根据失效的现象/模式,通过分析和验证,模拟重现失效的现象,找出失效的原因,挖掘出失效的机理的活动。 同时还讲到失效模式和失效机理,并且以LED和贴片电阻做为举例。 失效模式是失效…

Flutter:页面滚动

1、单一页面,没有列表没分页的,推荐使用:SingleChildScrollView() return Scaffold(backgroundColor: Color(0xffF6F6F6),body: SingleChildScrollView(child: _buildView()) );2、列表没分页,如购物车页,每个item之间…

facebook欧洲户开户条件有哪些又有何优势?

在当今数字营销时代,Facebook广告已成为企业推广产品和服务的重要渠道。而为了更好地利用这一平台,广告主们需要理解不同类型的Facebook广告账户。Facebook广告账户根据其属性可分为多种类型,包括个人广告账户、企业管理(BM&#…

WEB攻防-通用漏洞CSRFSSRF协议玩法内网探针漏洞利用

CSRF构造工具,也可以用bp构造 选中要保存的请求,点击Generate HTML,生成带有添加用户请求的html文件,然后将构造的html放在网站上,生成访问地址,诱导管理员点击链接,就会添加用户 start Recording之后就会…

C#面向对象之访问限制,类基础,继承

文章目录 1 访问限制1.1 简介 2 类基础讲解2.1 类定义2.2 构造函数2.2.1 构造函数2.2.2 静态构造函数2.2.3 初始化顺序2.2.4 对象初始化器 2.3 析构函数2.4 类的静态成员2.5 匿名对象2.5.1 定义2.5.2 匿名对象的创建 3 继承3.1 基类和派生类3.2 基类初始化3.3 Partial类3.3.1 定…

代码之丑第一期-缩进

各位小伙伴们,大家好!咱今天就算是正式开张了。实不相瞒,第一期的内容早已写好,但唯独这开篇方式,笔者想了好些时间,包括但不限于如下风格: 斗破苍穹式(已经三刷)&#…

JVM 性能调优 -- JVM常用调优工具【jps、jstack、jmap、jstats 命令】

前言: 前面我们分析怎么去预估系统资源,怎么去设置 JVM 参数以及怎么去看 GC 日志,本篇我们分享一些常用的 JVM 调优工具,我们在进行 JVM 调优的时候,通常需要借助一些工具来对系统的进行相关分析,从而确定…

linux上离线部署Mysql5.7.22

官网下载地址: https://downloads.mysql.com/archives/community/ Mysql安装步骤: 1.上传mysql安装包 上传 mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz 到服务器指定目录 2.解压缩 tar -zxvf mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz 3.修改名称 mv mysq…

日志与线程池

这里写自定义目录标题 日志Log.hpp测试main.cpp结果 线程池线程池的种类ThreadPool.hpp测试 Task.hpp 和 main.cppTask.hppmain.cpp结果 线程池的单例模式实现方式SignalThreadPool.hpp测试main.cpp 线程安全与重入问题死锁死锁的四个必要条件 日志 日志需要包含的信息 • 时间…

1.1 数据结构的基本概念

1.1.1 基本概念和术语 一、数据、数据对象、数据元素和数据项的概念和关系 数据:是客观事物的符号表示,是所有能输入到计算机中并被计算机程序处理的符号的总称。 数据是计算机程序加工的原料。 数据对象:是具有相同性质的数据元素的集合&…

泷羽sec学习打卡-shell命令5

声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都 与本人无关,切莫逾越法律红线,否则后果自负 关于shell的那些事儿-shell5 字符串运算符逻辑运算符之布尔运算符实践是检验真理的唯一标准 字符串运算…

Elasticearch索引mapping写入、查看、修改

作者:京东物流 陈晓娟 一、ES Elasticsearch是一个流行的开源搜索引擎,它可以将大量数据快速存储和检索。Elasticsearch还提供了强大的实时分析和聚合查询功能,数据模式更加灵活。它不需要预先定义固定的数据结构,可以随时添加或修…

Mybatis Plus 增删改查方法(一、增)

先定义一个简单的测试表,执行脚本如下: create table user(id bigint primary key auto_increment,name varchar(255) not null,age int not null default 0 check (age > 0) ); 根据Spingbootmybatisplus的结构根据表自行构建结构,大致…