Java中多线程经典案例

案例一单例模式

只有一个对象,只实例化一个对象

        饿汉模式

        在程序开始初期的实例化一个对象 

static成员初始化时机是在类加载的时候,static修饰的instance只有唯一一个,初始化也是只执行一次,static修饰的是类属性,就是在类对象上的,每个类对象在JVM中只有一份,里面的静态成员也是只有一份

后续想要获得instance,就可以调用getInstance来获得已经new好的这个对象,就不需要重新new

懒汉模式

不是在程序启动的时候创建实例,而是在第一次使用的时候才去创建

核心单例模式

万一,其他代码想new一个这个类的实例怎么办,我们要禁止其他的代码new这个类的实例,只需将构造方法前面加private即可,因为其他代码想实例化一个对象,势必需要调用构造方法,那么这个构造方法是私有的,只能在SingleTon这个类中调用构造方法,在其他代码中调用这个构造方法势必会编译错误

s1和s2都是调用类里面的静态方法,将instance这个对象返回给s1,s2,所有他们所指向的对象都是一样的,打印s1一定等于s2

关于懒汉和饿汉的线程安全问题

结合代码来看,首先懒汉模式是线程不安全的,而饿汉模式是线程安全的,因为在Java程序刚开始运行的时候instance对象就被创建好,多线程中饿汉模式只是读取了instance的值,并没有修改,多个线程读取同一变量的值是不会引起线程安全问题的

懒汉模式线程不安全的原因

相反懒汉模式则会引起线程不安全,因为懒汉模式是在有需要的时候才会去实例化对象,在多线程编程中懒汉模式要进行读和写操作,这一修改变量的值不是原子的,所有会造成线程不安全的问题,因此我们要使用synchronized对读和写操作进行加锁,打包成一个原子的操作,加锁会影响性能的问题,如果是第一次判断instance是否为空则需要加锁,若后续第二次...等,每次加锁都势必会影响性能问题,因此我们可以考虑在加一个if判断

双重if判断含义不同,第一层if判断是判断是否需要加锁,在实例化之后instance有了自己的值就无需加锁,在实例化之前就需要加锁,假如有t1和t2两个线程并发执行上述代码,第一次执行第一个if由于instance的值为null,所有两个线程的if判断都为真进入if语句内,此时遇到synchronized,这个两个线程都是同一个锁对象,假设t1线程先获取到锁,此时t2进入阻塞等待,t1执行第二个if判断,instance为null判断为真,则new一个对象赋值给instance,此时instance有了自己的值不再为空,锁的代码块解锁,t1线程释放锁,t2线程获取到锁,进行if判断,此时instance有了自己的地址不在为空,所以这个判断为假,不在实例化对象,所以这样就做到了只实例化一个对象达成了目的,当第二次执行上述代码时,由于instance不为null,所以直接return instance的值,这样我们就是读取了这个变量的值并没有进行修改操作,从而线程安全

还有一个需要考虑的问题那就是内存可见性的问题,那就是编译器会优化代码,把instance的值存到寄存器当中,每次从寄存器读取instance的值,从而使每次instance的值都为null,这就会使我们的new对象操作变得无意义,为了防止这个问题,我们也可以在instance加入volatile修饰,防止它被编译器优化掉.

面试考

这一个new操作对应三个操作1.申请内存,2.调用构造函数3.把地址赋值给引用,

延伸(了解即可)
案例二:阻塞等待

        之前所学的队列是最基础的队列,在实际开发中还有许多变种队列

        1.队列,先进先出

        2.优先级队列,具有优先级的先出去.

        3.BlockingQueue(标准库提供)阻塞队列,先进先出,线程安全,具有阻塞功能,阻塞(1.当队列为空的时候,尝试出队列,出队列操作会阻塞,一直阻塞到队列不为空的时候,2.当队列满的时候,尝试入队列,入队列操作就会阻塞,一直阻塞到队列不为满时,)

        4.消息队列,不是普通的先进先出,而是通过topic这样的参数来对数据进行归类,出队列的时候,指定topic,每个topic下的数据先进先出,也具有阻塞特性

消息队列的作用:实现生产者消费者模型

        如图所示,上述A与B直接进行调用,意味这A中有关于包含B的逻辑,B中包含有关于A的逻辑,此时彼此之间产生了一定的耦合,当对A进行修改时,会影响到B,如果修改B一定会影响到A,

当我们引入消息队列之后实现生产者消费者模型的好处

      1.能够使程序解耦合:,使程序之间的关联性不大

服务器A之间与消息队列mq进行交互,并不知道服务器B的存在,服务器B之间与消息队列mq进行交互,并不知道服务器A的存在,服务器A,B只关心与队列的交互,此时对A修改势必影响不到B,如果服务器A挂了,也不会影响到B,此时如果以后需要引入服务器C,直接让服务器C在队列里面读取数据即可,不需要对服务器A进行修改. 

        2.削峰填谷:

                客户端发来的请求,个数多少是未知的,没办法提前预知,遇到特殊情况,就可能导致客户端给服务器的请求激增

正常情况下A收到一个客户端请求,就需要请求一次B,请求激增的话,由于A的工作比较简单,消耗的资源少,但是B的工作复杂,占用的资源多,就容易挂(服务器每处理一个请求,就需要消耗一定的系统资源,同一时刻处理太多的请求,消耗的总资源超过了机器提供的,则就会卡死)

此时我们引入消息队列mq,无论A的请求有多少,都不会影响B,他们通过消息队列进行交互,有了消息队列mq,此时无论A写的有多快,B都可以按照固定的节奏来消费数据,B的节奏就不会跟着A,相当于mq给服务器B的保护起来

缺点:

最大的缺点就是效率,多了一次访问,和网络通信,效率折损,不适合响应速度特别高的场景

通过代码实现阻塞队列以及消费者生产者模型

阻塞队列

Blocking是一个接口,不能直接通过new来实例化对象,我们需要new实现这个接口的类

上述这三个类是实现Blocking接口的类,

阻塞队列常用的方法put()和take(),这两个方法具有阻塞特性

使用put会抛出Interrupted异常,因为put会引起阻塞,阻塞过程中其他线程尝试终止put,此时put会抛出异常

消费者生产者模型

自主实现阻塞队列

 if存在的问题,如果将wait唤醒之后,他会继续向下执行下面的逻辑,不会在进行判断,如果此时数组满了,则它会将之前的值给覆盖掉,不安全,所以保险起见应该用while来代替if,这样就能在wait醒来之后,在进行一次判断看是否符合条件.

案例三:线程池

        池就是为了提高效率存在,包括常量池,线程池,进程池,内存池数据库,连接池,把需要用到的资源提前准备好,放入到池子里面

并发编程使用多进程,就可以了,由于线程比经常更轻量,频繁的创建和销毁,更有优势,随着时代的发展1s内服务器处理的请求变得越来越多,所以线程频繁的创建和销毁的开销也变得越来越明显,

如何优化?

        1)线程池

        2)协程(纤程)

为什么引入线程池

创建线程/销毁线程,是用户态和内核态配合完成的工作,

线程池/协程,创建销毁只需要用户态不需要内核态,

调用系统api,来进行创建/销毁线程这个过程需要内核,内核完成的工作属于是不可控的,

如果使用线程池,提前把线程创建好,放在用户态代码完成的数据结构当中,后面用的时候直接从里面取,不用的时候再放回池子里去,这个过程完全是用户态代码,就不用与内核进行交互

协程本质上也是纯用户态的操作,规避内核操作,不是在内核里把线程提前创建好,而是利用内核的一个线程来表示多个线程(纯用户态,进行协程之间的调度)     

线程池

在Java标准库中的ThreadPoolExecutor,这个类使用起来特别复杂,因为在构造方法中有特别多的参数,我们需要了解到各个参数的含义,面试中会考

Java文档中,在util>concurrent

参数最多的构造方法,我们来了解一下这七个参数

第一个参数corePoolSize核心线程数

第二个参数maximumPoolSize最大线程数(核心线程+非核心线程)

第三个参数keepAliveTime非核心线程允许空闲的最大时间

第四个参数unit是停留的时间(s,day,mintue)枚举类型

第五个参数workQueue线程池的任务队列,线程池会提供submit方法,让其他的线程把任务提供给线程池,线程池内部需要这样一个队列的数据结构,把要执行的任务保存起来,后续线程池内部的工作线程就会消耗这个队列,从而完成具体的任务,执行Runnable里面的run方法

第六个参数threadFactor,标准库提供的创建线程的工厂,这个线程工厂,主要就是为了批量的给要创建的线程设置一些属性啥的,线程工厂,在工厂方法中,把线程的属性提前初始化好了,主要搭配线程池来用

工厂设计模式,其实也是一种设计模式,解决构成方法创建对象太坑了的问题,实例化对象时,使用构造方法,但是由于构造方法必须与类名相同,所有我们只能通过重载来实现不同的构造方法,然后这样就会有局限性例如

class Point{

}

public Point(double x, double y)(笛卡尔坐标系)

public Point(double r, double α)(极坐标系)

上述这个构造方法实例化对象的例子是会编译报错的,因为上述方法没有构成重载

我们可以通过静态方法包装一下构造方法成一个静态方法,工厂模式代码,规避了上述构造方法不能重载的问题,这个方法就是工厂方法,这样写代码的套路就是工厂模式

第七个参数最重要的参数handler

handle不是句柄的意思,句柄是一个资源标识,这里的handle是拒绝策略,这个其实是一个枚举类型,

如果当前任务队列满了,采用什么拒绝策略呢?在标准库中提供了四种方法,下面这张图就是

四种策略

第一种方法AbortPolicy直接抛出异常(任务处理不过来)

第二种方法CallerRunsPolicy由调用者负责执行,如果队列满了&&调用了submit入队列,那么调用者就自己执行Runable的run方法

第三种方法DiscardOldestPolicy,丢掉最老的任务,让新的任务去队列种排队

第四种方法DiscardPolicy,丢掉最新的任务,按照原来的节奏执行

标准库线程池中设定是这样的,把线程分成两类1.核心线程(corePoolSize),2.非核心线程,

这里面就涉及到动态扩展,一个线程池刚被创建出来的时候,里面就包含核心线程数这么多的线程,在刚开始任务比较少的时候,这这几个核心线程数就够用,线程池中提供了一个方法submit(),可以添加任务,每个任务都是一个RUNNABLE,如果任务太多了,这四个线程不够了就会自动创建新的线程,来支持更多的任务

在线程池中,创建的线程不能超过最大线程数,如果现在的任务不是很重,那么可以把非核心线程数回收,线程池中线程数目必须不少于核心线程数,线程池是用来降低创建销毁线程的频次的,而不是不完全销毁线程

非核心线程要在线程池不忙的时候回收掉,而不是立即回收,例如停留时间3s内没有任务可以做,就可以被回收了

在实际开发中线程个数设置多少合适?

根据实验的方式来进行找到一个合适的值,对程序进行性能测试,最终根据实际的响应速度跟系统开销找到最合适的值.

根据程序的特点来进行设置极端一点可以分为两大类

1.CPU密集型,代码逻辑都需要通过CPU来进行干活,线程数目不应该超过CPU逻辑核心数

2.IO密集型,代码大部分时间在等待IO操作,不消耗CPU不参与调度,瓶颈不在CPU,更多考虑的是网络带宽

由于标准库也知道ThreadPollExecuter使用起来费劲,所有自己提供了一个工厂类方法Exceutors,下面是线程池提供的工厂方法

常用的包装类

自己实现一个线程池

线程池包括1.若干个线程,2.任务队列,3.submit方法

案例四:定时器

定时器在Java标准库中定义的类是Timer,定时器顾名思义就如同生活中的闹钟一样,比如下午2点去上班,我们一点睡觉,则我们可以定一个闹钟去定时一个小时,以后去执行上班任务,在代码中也是,Timer提供了一个schedule方法,我们可以添加一定时间以后需要执行任务,通过重写run方法来实现.

模拟定时器功能

需要定义一个类,用来执行任务基于Runnable的run方法实现,定义一个成员变量runnable调用run方法来实现任务,还有一个变量time这个是用来记录任务执行的决定时间的,实现比较器接口,从而保证这个优先级队列是小根堆

还需要一个数据结构来保存上述任务,

上述数据结构是优先级队列,为什么不使用阻塞队列,因为如果使用阻塞队列就得再加一把锁,同时有两把锁可能会造成死锁的问题,所以我们自己手动设定一把锁是最好的选择,在这个类里面,有一个schedule方法是增加任务到这个优先级队列当中,我们这个构造方法是用来扫描队列中的每个任务是否到达了指定时间,如果队列元素为空的时候和没到执行任务的时候,这个线程就会wait,不去占用cpu的资源,当有新的任务进来就会立即开始工作,用notify进行唤醒,而用sleep的话,就是彻底睡眠,有新任务进来也会在睡眠,使用interrupt唤醒是非常规手段,所以使用wait,由于这两个线程同时对队列进行修改操作,可能会造成线程不安全,所以需要加锁

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

下载安装JavaFX及解决报错:缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序|Eclipse

目录 1.下载并解压 2.Eclipse配置 3.报错问题 解决方法1:将javaSE更改到9以下 解决方法2: 使用module-info.java配置解决 1.下载并解压 JavaFX下载地址:JavaFX - Gluon 选择合适自己电脑配置的sdk版本下载 打不开网页的参考这个博客&…

Demeditec Diagnostics — AMH ELISA试剂盒

抗缪勒氏管激素(AMH),是一种二聚体分子量为140 KDa的糖蛋白,是转化生长因子-β (TGF-β)细胞因子家族,在生殖结构正常分化中起重要作用。AMH已被被确定为卵巢储备的可靠标志,有助于预测早期卵泡丢失和更年期开始。AMH水平也反映了…

【Python画图-seaborn驯化】一文学会seaborn画因子变量图catplot函数使用技巧

【Python画图-seaborn驯化】一文学会seaborn画因子变量图catplot函数使用技巧 本次修炼方法请往下查看 🌈 欢迎莅临我的个人主页 👈这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合,智慧小天地! 🎇 免费获取相关内…

【运营版】公众号接口回调出租用出售微信公众号多域名无限回调授权系统+接口文档

此系统用于微信无限回调单个用户授权,如你的无限回调借给他人使用,怕他人泛滥您的无限回调,导致您的域名或者公众号经常封,那么你们可以用此系统给他们设置一个授权使用权限,如给指定域名添加授权登录,那么…

【Linux】目录的相关命令——cd,pwd,mkdir,rmdir

1.相对路径与绝对路径 在开始目录的切换之前,你必须要先了解一下所谓的路径(PATH),有趣的是:什么是相对路 与绝对路径? 绝对路径:路径的写法“一定由根目录/写起”,例如:/usr/shar…

间接平差——以水准网平差为例 (matlab详细过程版)

目录 一、原理概述二、案例分析三、代码实现四、结果展示本文由CSDN点云侠原创,间接平差——以水准网平差为例 (matlab详细过程版),爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT生成的文章。 一、原理概述 间接平差的函数模型和随机模型…

深入分析 Android BroadcastReceiver (十)(完)

文章目录 深入分析 Android BroadcastReceiver (十)1. 深入理解 Android 广播机制的高级应用与实践1.1 高级应用1.1.1 示例:广播启动服务1.1.2 示例:数据变化通知1.1.3 示例:下载完成通知 1.2 实践建议1.2.1 设置权限1.2.2 动态注册和注销广播…

零基础STM32单片机编程入门(十) 28BYJ-48步进电机的控制实战含源码视频

文章目录 一.概要二.28BYJ-48步进电机介绍三.步进电机的主要特性四.步进电机驱动1.驱动硬件介绍2.四相八拍程序控制介绍 五.STM32单片机驱动步进电机正转反转实验六.CubeMX工程源代码下载七.讲解视频链接地址八.小结 一.概要 步进电机是将电脉冲信号转变为角位移或线位移的开环…

102.二叉树的层序遍历——二叉树专题复习

迭代方式&#xff1a; class Solution {// 定义一个成员变量res来存储层序遍历的结果List<List<Integer>> res new ArrayList<>();// levelOrder方法是层序遍历的接口&#xff0c;它接受一个二叉树的根节点rootpublic List<List<Integer>> lev…

开展新版FMEA培训如何避免陷入形式主义?

在企业中开展新版FMEA培训&#xff0c;旨在提升员工对产品潜在故障及其影响的识别、评估和控制能力&#xff0c;从而增强产品质量和可靠性。然而&#xff0c;不少企业在开展新版FMEA培训时往往容易陷入形式主义&#xff0c;导致培训效果不佳。为了避免这种情况&#xff0c;我们…

视频压缩软件哪个压缩最小,视频用什么软件压缩最小

在数字媒体时代&#xff0c;视频内容的生产与分享已成为生活常态。但随之而来的问题就是&#xff0c;大视频文件占用过多存储空间&#xff0c;上传和分享也变得不便。本文将为你揭示如何将视频压缩到最小&#xff0c;同时保持画质清晰。让我们一起探索吧&#xff01; 下载并文件…

Java SE 9模块化系统。

JDK 9模块化介绍 介绍Java SE 9&#xff1a;拼图项目1.拼图项目将引入Java SE 9的全新概念&#xff1a;Java模块系统。2.Java模块解决了什么问题3.细节描述 Java SE 9模块系统的优点1.由于Java SE 9将把JDK&#xff0c;JRE&#xff0c;JAR等分成较小的模块&#xff0c;因此我们…

程序员必知的 89 个操作系统核心概念

1. 操作系统&#xff08;Operating System&#xff0c;OS&#xff09;&#xff1a;是管理计算机硬件与软件资源的系统软件&#xff0c;同时也是计算机系统的内核与基石。操作系统需要处理管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系…

Spring Boot源码概述及应用案例

文章目录 Spring Boot源码关键点应用案例&#xff1a;构建一个简单的Spring Boot Web应用1. 创建项目2. 主类3. 添加用户实体4. 创建用户服务5. 创建REST控制器6. 运行和测试应用 深入与扩展1. 数据持久化2. 安全性增强 Spring Boot源码关键点 Spring Boot 的源码设计遵循模块…

SQLServer的系统数据库用别的服务器上的系统数据库替换后做跨服务器连接时出现凭证、非对称金钥或私密金钥的资料无效

出错作业背景&#xff1a; 公司的某个sqlserver服务器要做迁移&#xff0c;由于该sqlserver服务器上数据库很多&#xff0c;并且做了很多的job和维护计划&#xff0c;重新安装的sqlserver这些都是空的&#xff0c;于是就想到了把系统4个系统数据库进行替换&#xff0c;然后也把…

LeetCode热题100刷题8:54. 螺旋矩阵、73. 矩阵置零、48. 旋转图像

54. 螺旋矩阵 class Solution { public:vector<int> spiralOrder(vector<vector<int>>& matrix) {vector<int> vec;if(matrix.empty())return vec;int left0;int right matrix[0].size()-1;int up0;int down matrix.size()-1;while(true) {for(i…

flask、fastapi在服务器制作接口携参访问返回参数

flask创建接口&#xff1a; 一、安装python 官网下载Download Python | Python.org 二、安装flask 在选择的文件夹路径cmd调用bash安装 pip install Flask三、创建flask应用 # app.py from flask import Flask, request, jsonify app Flask(__name__) app.route(/ech…

如何选择合适的PCB表面处理工艺?

在PCB制造中应用PCB表面处理至关重要&#xff0c;以保护铜迹线不受氧化和环境污染物的侵蚀&#xff0c;这些污染物会降低性能。这些PCB表面处理可以防止水分、灰尘、化学物质和极端温度的侵入&#xff0c;防止PCB材料的腐蚀。它们还有助于在组装过程中有效焊接和粘合&#xff0…

240707_昇思学习打卡-Day19-基于MindSpore通过GPT实现情感分类

240707_昇思学习打卡-Day19-基于MindSpore通过GPT实现情感分类 今天基于GPT实现一个情感分类的功能&#xff0c;假设已经安装好了MindSpore环境。 # 该案例在 mindnlp 0.3.1 版本完成适配&#xff0c;如果发现案例跑不通&#xff0c;可以指定mindnlp版本&#xff0c;执行!pip…

Git 查看、新建、删除、切换分支

Git 是一个版本控制系统&#xff0c;软件开发者用它来跟踪应用程序的变化并进行项目协作。 分支的诞生便于开发人员在彼此独立的环境中进行开发工作。主分支&#xff08;通常是 main 或 master&#xff09;可以保持稳定&#xff0c;而新的功能或修复可以在单独的分支中进行开发…