多线程.下

news2024/9/20 22:01:55

目录

1.线程等待

2.join()介绍

3.获取当前对象引用

4.线程的状态

5.线程安全

6.synchronized()关键字

7.synchronized关键字底层介绍


1.线程等待

对于操作系统而言,内部多个线程的执行是“随机调度,抢占式执行”的。简而言之线程的等待是在确定线程的“结束顺序”。在操作系统中,虽然无法确定哪个线程执行的顺序以及执行的频率,但是可以控制哪个线程先结束。

例如现有两线程A,B;若果在A线程中调用B线程,B.join();

意思就是就是控制A线程等待B线程执行结束后再执行。哪个线程线程调用join();哪个线程就先执行。

如举例所示:在 t.start(); 之后本来应该两个线程一起执行的,但是经过 t.join();main 线程发生堵塞,只有 t 线程执行,等到 t 结束后,join才会返回,main线程接着执行。

调整一下,如果先让 t 结束,然后main才开始执行,这个时候才开始join() 是否main会堵塞呢

可以发现,如果 t 线程已经执行结束,主线程再次调用 t.join(); main线程也不会发生阻塞,因为join();就是为了确保能够先结束,如果已经在join();之前结束,join()就不必再等待了。

注意:此处仅仅是main线程等待t1,t2,但是t1,t2之间没有等待关系

总结:任何线程之间都是可以相互等待的,并不是只能主线程等待别人,线程等待并不是两个线程之间,一个线程可以等待多个线程。


2.join()介绍
方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束最多等待millis毫秒
public void join(long millis,int nanos)同理,但是精度更高

public void join(long millis) :millis十毫秒级的时间,设置等待时间一旦超过时间就会自己结束

public void join(long millis,int nanos):一个时间的范围,nanos是更高精度的纳秒。一般精度只到ms级别,再往上计算机就很难做到了。精度越高,开销就越大。

join();方法有多种重载的方法,上一标题介绍的是无参数版本无参数就意味着没有等待时间限制,如果被等待的线程发生阻塞结束不了,那么join();就会一直等待。这是一个十分危险的操作。

用于军工领域的计算机系统“实时操作系统”,可以把调度的开销降到很低但是符合一定误差要求,从而获取更高精度。舍弃很多功能换来了高精度实时性


3.获取当前对象引用

在某个线程中,想要获取自身的Thread对象的引用,就可以使用currentThead()方法来获取。

在其他线程中想要调用main线程,不采用currentThread();好像很难获取main线程的引用,想要获取当前线程的引用只需要在当前线程调用currentThread即可。

任何线程中都可以通过这样的操作,拿到线程的引用,线程的终止也是通过调用当前线程的引用里的方法: Thread.currentThread.isInterruptted()  


调用Thread.sleep(); 方法让线程阻塞等待,是具有一定时间的。

线程执行sleep,就会使线程不参与cpu调度,从而把cpu的资源让出来给别的操作使用。

这样的sleep操作称为“放权” 操作。在有的场景中,发现某个线程占用的cpu资源很高但是又使用的很少就可以通过sleep休息短暂的时间来改善

线程的优先级也可以产生此影响,但是影响是有限的。通过sleep更加明显的影响到cpu占用。


4.线程的状态

java中对于线程的状态做出了更明晰的划分,不只有阻塞和就绪两种状态。

1.NEW:当你使用 new Thread() 创建了一个线程对象,但还没有调用 start() 方法时,线程处于 NEW 状态。

2.TERMINATED:当线程执行完 run() 方法,或者在 run() 方法中抛出未捕获的异常,它就会进入 TERMINATED 状态。

3.RUNNABLE:当你调用了 start() 方法后,线程就会进入 RUNNABLE 状态,它表示线程已准备就绪,等待被操作系统调度。

4.BLOCKED: 一个进程试图获取其他进程所持有的锁时,他就会处于这个状态。直到这个锁释放

5.TIMED_WAITING:有具体的时间等待

6.WAITING:没有具体的时间等待


5.线程安全

举例:多个线程同时执行一个代码的时候可能会引起一些bug,理解线程安全是解决或者避免bug的关键。

上述代码并没有正确相加得出100000,下面分析原因。

此处的count++,在cpu看来是3个指令(下面的指令在不同编译器中写法不同)

1.把内存的数据读取到cpu寄存器里:load

2.把cpu寄存器的数据+1                  :add

3.把寄存器里的值写回内存             :save

但是因为是三个指令,cpu会出现只执行了其中一个或者两个,剩下的指令就会被调度走。这样就会容易出现bug

根本原因是因为:

1.线程在操作系统中随机调度,抢占式执行,

2.多个线程同时修改一份变量

3.修改的操作不是“原子”的(原子的:不可分割的最小单位),在cpu的视角,一条指令就是不可分割的最小单位,cpu在切换线程的时候只能确保执行完一条指令。

4.内存可见性,指令重排(下节介绍)

只有第一张图中的两种方式是正确执行的,第二张图就是错误的。指令的调度有很多种顺序,除了第一张图中的两种顺序是正确的,其他任何执行顺序得到的结果都是不正确的。

如何解决上述问题?

那就要从原因下手了,线程在操作系统的随机调度抢占式执行是很难干涉的,其次如果多个线程能同时修改同一变量也是不可控制的,因为这也是操作系统多线程的特性。但是如果修改的操作不是原子的就可以。使分开执行的指令一次性执行完就好了。

比如count++中,让数据的读取,修改,写回内存都是原子性的,其他线程的指令插入不进来就可以了。

6.synchronized()关键字

这个关键字后面的()并非填的是”参数“,而是填入的是一个指定的锁对象,通过锁对象来进行判定,锁对象可以是任何对象。

{} 内部就是要一同执行的整体,在执行的时候,其他线程的代码的逻辑插入不进来。

值得注意的是,想要针对修改同一变量的线程加锁,这些线程所持有的锁对象必须一致是同一对象。不然加锁无效!!

由于t1和t2 都是针对locker对象加锁的,t1先加锁成功,t1就直接执行 {} 里的代码

t2也加锁了,但是比t1慢上一步,当t2发现对象已经被别人先锁起来了,那么t2只能等到t1 的{}执行结束释放锁后,t2再加锁。

又因释放锁unlock一定是在save之后,确保了t1的count++的结果可以正确写入内存,两者的count++不会穿插执行,也就不会覆盖掉对方的结果了。

加锁本质上是把 局部随机并发执行的代码 强行变成了串行,从而解决线程安全问题。

注意:

1.锁对象的作用是区分两个线程或者多个线程是否针对同一个对象加锁。都是同一对象的锁就会出现“阻塞”(锁竞争)。所加的锁不是同一对象那么多个线程还是并发执行。

2.锁对象必须是对象,是引用类型Object类或者其子类,不能使int,double这种内置类型。

3.加锁后代码只是局部代码穿行,但效率依然比join要快。只有锁里面的是串行,其他部分代码不影响并发执行。


7.synchronized关键字底层介绍

synchronized()关键字是jvm提供的功能,底层实现就是通过C++代码编写的,也是依靠操作系统提供的API实现的加锁,操作系统的API是来自由于cpu上支持特殊的指令实现的

操作系统原生的API就是两个函数lock()/unlock(),大多数编程语言是类似于封装的方式来使用这两个函数,但是java中直接通过一个关键字来同时完成加锁解锁,这样的好处是在编写代码的时候最后很有可能会忘记unlock解锁,这个关键字会自动替你解锁。就算直接trturn也会帮你释放锁再return。

synchronized()里面可以是任何对象,最偷懒的写法就是直接某个类.class(类对象),但是偷懒需要付出的代价就是代码效率会降低。

一个类对象可以获取到这个类里面的详细情况,包括但不限于类有哪些属性,方法,属性是什么类型,什么名字,方法是什么类型,返回类型,这个类实现了哪些接口等等。这就是反射,反射是一组API可以对上述信息获取或者修改

synchronized 还可以修饰一个方法:

如果修饰类方法就没有this,就是直接给类对象加锁。

注意:在多线程中并非就是写了synchronized就是安全的,还要看具体代码怎么写,是否要加synchronized是要看具体场景。

比如StringBuffer,Vector,Hashtable都不推荐,因为加了太多的锁,会导致代码效率降低。


总结:synchronized的几种使用方式:

1.synchronized(){ };圆括号指定锁对象

2.synchronized 修饰一个普通方法相当于针对this加锁。

3.synchronized 修饰一个静态方法,相当于对类对象加锁。

可以把任意Object子类或者Object类的对象作为锁对象,锁对象是什么不重要,重要的是多个线程的对象是否同一个,是同一个才会出现锁竞争。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

孟浩然,山水田园一山人

孟浩然,字浩然,号孟山人,生于唐睿宗永昌元年(公元689年),卒于唐玄宗开元二十八年(公元740年),享年51岁。他出生于盛唐时期的襄州襄阳(今湖北省襄阳市&#xf…

网络安全常见错误及解决办法(更新中)

# 开启代理,无法连接网络 把代理关掉 # 上一秒还在安装tree,下一秒xshell就连接不上了 —》sshd服务的key这个文件权限过高,跟装tree没有关系,装一个epel 源,epel-release​ 部分命令:chmod 600 /etc/ssh…

QXlsx读写excel

QXlsx读写excel 安装 QXlsx使用 qmake使用 CMake 基本用法1. 写入 Excel 文件2. 读取 Excel 文件 详细用法1. 设置单元格样式2. 合并单元格3. 创建图表4. 设置列宽和行高 完整示例 QXlsx 是一个用于在 Qt 应用中读写 Excel 文件的第三方库。它提供了丰富的 API,可以…

人工智能与社交变革:探索Facebook如何领导智能化社交平台

在过去十年中,人工智能(AI)技术迅猛发展,彻底改变了我们与数字世界互动的方式。Facebook作为全球最大的社交媒体平台之一,充分利用AI技术,不断推动社交平台的智能化,提升用户体验。本文将深入探…

昇思25天学习打卡营第23天|ResNet50图像分类

课程打卡凭证 ResNet网络 ResNet(Residual Networks,残差网络)是一种深度神经网络结构,它的核心思想是引入了“残差学习”来解决深度网络中的退化问题。在深度神经网络中,当网络层数增加到一定程度时,网络…

解读:基于图的大模型提示技术

【引子】大模型的兴起, 使得读论文成为了学习中的一种常态。如果一篇论文没有读懂,不用担心,可以再读一篇该领域内容相近的论文,量变可能会产生质变。就像编程语言一样,你永远无法精通一门编程语言,除非&am…

【Gin】精准应用:Gin框架中工厂模式的现代软件开发策略与实施技巧(上)

【Gin】精准应用:Gin框架中工厂模式的现代软件开发策略与实施技巧(上) 大家好 我是寸铁👊 【Gin】精准应用:Gin框架中工厂模式的现代软件开发策略与实施技巧(上)✨ 喜欢的小伙伴可以点点关注 💝 前言 本次文章分为上下两部分&…

linux进程优先级——优先值、调度算法、进程性质

前言:本篇内容主要讲解linux下进程的优先级。 优先级的内容相对较少, 最重要的内容就是cpu的调度方法。 内容相对容易理解。 ps:本节内容适合了解冯诺依曼和操作系统的管理方式以及进程PCB的友友们进程观看 进程的优先级是什么 进程的优先级…

【制作100个unity游戏之31】用unity制作一个爬坡2d赛车小游戏

最终效果 【制作100个unity游戏之31】用unity制作一个爬坡2d赛车小游戏 前言 今天用unity制作一个简单的爬坡2d赛车小游戏 素材 https://www.spriters-resource.com/mobile/hillclimbracing/ 拼装车素材 车身添加碰撞体,摩檫力0 轮胎添加碰撞体和刚体&#xff0…

CVE-2020-7248 OpenWRT libubox堆栈溢出漏洞复现(更新中)

提要 该文档会一直处于更新当中,当状态为完毕后,才是更新完成。由于网络上关于该漏洞原理的分析文档和资源实在是太少,而本人关于该方向也才是刚入门,能力有限,所以复现需要的时间较长,需要补充和学习的东西…

《从C/C++到Java入门指南》- 9.字符和字符串

字符和字符串 字符类型 Java 中一个字符保存一个Unicode字符,所以一个中文和一个英文字母都占用两个字节。 // 计算1 .. 100 public class Hello {public static void main(String[] args) {char a A;char b 中;System.out.println(a);System.out.println(b)…

Spring通过工厂方法进行配置

在Spring的世界中, 我们通常会利用 xml配置文件 或者 annotation注解方式来配置bean实例! 在第一种利用 xml配置文件 方式中, 还包括如下三小类 反射模式(我们前面的所有配置都是这种模式)工厂方法模式Factory Bean模…

跳妹儿学编程之ScratchJr(10):结束积木篇—放学回家

博主资深软件架构师,拥有13年大型软件与互联网系统开发、设计和架构经验,曾就职于华为,现任职于国内知名互联网公司。平时在家教咱家“跳妹儿”编程,并将心得和过程记录下来。希望可以帮助更多对编程感兴趣的家庭。 前言 继上一篇…

FastDFS分布式存储

文章目录 FastDFS原理FastDFS架构文件的上传文件的下载 FastDFS原理 FastDFS架构 Tracker server 跟踪服务器主要做挑度工作,起到均衡的作用,负责管理所有的Storage server和group Storage server 储服务器,主要提供容量和备份服务;以group为单位&…

VMware虚拟机下安装Ubuntu(详细教程,最小系统的安装,含VMware Tools)

1.VM的下载安装 VMware的下载安装教程_vm16 pro下载-CSDN博客 2. Ubuntu 下载 在官网或者镜像站下载所需版本的.ios镜像,这个镜像在接下来的步骤中会用到: Ubuntu 22.04.4 LTS 下载 和 清华大学开源软件镜像站 - Ubuntu 22.04.4 下载 3. 创建虚拟机 […

【开发实战】QT5 + OpenCV4 开发环境配置应用演示

前言 作为深度学习算法工程师,必须要掌握应用开发技能吗?搞工程肯定是必须要会界面开发,QT就是一个很不错的选择。本文以QT5.15 OpenCV4.8 OpenVINO2023为例,搭建应用开发环境,演示深度学习模型的QT应用案例。 开发…

重拾CSS,前端样式精读-函数(颜色,计算,图像和图形)

前言 本文收录于CSS系列文章中,欢迎阅读指正 在计算机编程中,函数有着重要的作用和意义,它可以实现封装,复用,模块化,参数等功能效果,在如何在CSS中写变量?一文带你了解前端样式利…

聊聊HuggingFace Transformer

项目组件 一个完整的transformer模型主要包含三部分:Config、Tokenizer、Model。 Config 用于配置模型的名称、最终输出的样式、隐藏层宽度和深度、激活函数的类别等。 示例: {"architectures": ["BertForMaskedLM"],"atte…

蓝屏死机不再怕!CrowdStrike故障修复指南中心上线!

系统之家于7月22日发出最新报道,安全公司CrowdStrike因其Windows更新引发全球 850 万台电脑蓝屏死机问题后,上线了全新的“修复和指南中心”(Remediation and Guidance Hub),该中心汇集了与其错误更新相关的详细信息&a…

昇思25天学习打卡营第25天 | RNN实现情感分类

学习心得:RNN实现情感分类 在自然语言处理(NLP)的领域中,情感分类是一个极具挑战性的任务,它要求模型能够准确地从文本中识别出情感倾向。通过使用MindSpore框架和RNN模型进行情感分类,我获得了许多有关构…