多线程.下

目录

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.mfbz.cn/a/843014.html

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

相关文章

pico+unity3d 射线交互教程

前期配置:环境配置参考教程一,手部模型参考教程二,场景基于上一篇搭建。 最终效果:手部射线(初始不可见)对准 UI 显示,按下手柄 Trigger 键与可交互 UI(如 Button、Toggle、Slider …

数据结构——栈(顺序结构)

一、栈的定义 栈是一种数据结构,它是一种只能在一端进行插入和删除操作的特殊线性表。这一端被称为栈顶,另一端被称为栈底。栈按照后进先出(LIFO)的原则进行操作(类似与手枪装弹后射出子弹的顺序)。在计算…

【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理

初阶数据结构相关知识点可以通过点击以下链接进行学习一起加油!时间与空间复杂度的深度剖析深入解析顺序表:探索底层逻辑深入解析单链表:探索底层逻辑深入解析带头双向循环链表:探索底层逻辑深入解析栈:探索底层逻辑深入解析队列:探索底层逻辑深入解析循环队列:探索…

前端调试技巧:动态高亮渲染区域

效果: 前端界面的渲染过程、次数,会通过高亮变化来显示,通过这种效果排除一些BUG 高亮 打开方式 F12进入后点击ESC,进入rendering,选择前三个即可(如果没有rendering,点击橘色部分勾选上&…

ArrayList.subList的踩坑

需求描述&#xff1a;跳过list中的第一个元素&#xff0c;获取list中的其他元素 原始代码如下&#xff1a; List<FddxxEnterpriseVerify> companyList fddxxEnterpriseVerifyMapper.selectList(companyQueryWrapper);log.info("获取多个法大大公司数据量为&#…

深入理解Linux网络(三):TCP对象创建

深入理解Linux网络&#xff08;三&#xff09;&#xff1a;TCP对象创建 TCP对象创建inet_createsock_init_data TCP对象创建 常见的三句TCP编程&#xff1a; int main() {int sk socket(AF_INET, SOCK_STREAM, 0);connect(sk, ...)recv(sk, ...) }简单的两三⾏代码&#xff…

酷炫末世意境背景404单页HTML源码

源码介绍 酷炫末世意境背景404单页HTML源码&#xff0c;背景充满着破坏一切的意境&#xff0c;彷佛末世的到来&#xff0c;可以做网站错误页或者丢失页面&#xff0c;将下面的代码放到空白的HTML里面&#xff0c;然后上传到服务器里面&#xff0c;设置好重定向即可 效果预览 …

【面试题】Redo log和Undo log

Redo log 介绍Redo log之前我们需要了解一下&#xff0c;mysql数据操作的流程&#xff1a; 上述就是数据操作的流程图&#xff0c;可以发现sql语句并不是直接操作的磁盘而是通过操作内存&#xff0c;然后进行内存到磁盘的一个同步。这里我们必须要了解一些区域&#xff1a; 缓…

从安装Node到TypeScript到VsCode的配置教程

从安装Node到TypeScript到VsCode的配置教程 1.下载Node安装包&#xff0c; 链接 2.双击安装包&#xff0c;选择安装路径&#xff0c;如下&#xff1a; 3.一直点击下一步&#xff0c;直至安装结束即可&#xff1a; 这个时候&#xff0c;node会默认配置好环境变量&#xff0c;并且…

如何学习Hbase:糙快猛的大数据之路( 用讲故事的方式)

引言 还记得我刚踏入大数据领域的那天&#xff0c;就像一只初生的小鹿&#xff0c;对着HBase这座大山瑟瑟发抖。 但是&#xff0c;朋友们&#xff0c;让我告诉你一个秘密&#xff1a;学习就应该糙快猛&#xff01;不要追求一步到位的完美&#xff0c;在不完美中前进才是最高效…

/秋招突击——7/21——复习{堆——数组中的第K大元素}——新作{回溯——全排列、子集、电话号码的字母组合、组合总和、括号生成}

文章目录 引言复习数组中的第K大的最大元素复习实现参考实现 新作回溯模板46 全排列个人实现参考实现 子集个人实现参考实现 电话号码的字母组合复习实现 组合总和个人实现参考实现 括号生成复习实现 总结 引言 昨天的科大讯飞笔试做的稀烂&#xff0c;今天回来好好练习一下&a…

GIT命令学习 二

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…

opencv,连续拍摄多张图像求平均值减少噪点

对于照度低或者相机质量差造成的密集的随机小噪点&#xff0c;可以通过拍摄多张图像求平均值的方法来减少噪点&#xff0c;获得较为清晰的画面。 import cv2 import numpy as npclass FilterCamera:def __init__(self, cap, in_frame, num):self.cap cap # 定义的相机self.n…

Scaling Vision Transformers to 22 Billion Parameters

Scaling Vision Transformers to 22 Billion Parameters 主要贡献 Vision Transformer&#xff08;ViT&#xff09;的大规模扩展&#xff1a;尽管Transformer架构在自然语言处理&#xff08;NLP&#xff09;领域取得了巨大成功&#xff0c;但在计算机视觉&#xff08;CV&#…

NVidia 的 gpu 开源 Linux Kernel Module Driver 编译 安装 使用

见面礼&#xff0c;动态查看gpu使用情况&#xff0c;每隔2秒钟自动执行一次 nvidia-smi $ watch -n 2 nvidia-smi 1&#xff0c;找一台nv kmd列表中支持的 GPU 的电脑&#xff0c;安装ubuntu22.04 列表见 github of the kmd source code。 因为 cuda sdk 12.3支持最高到 ubu…

【JavaEE】AQS原理

本文将介绍AQS的简单原理。 首先有个整体认识&#xff0c;全称是 AbstractQueuedSynchronizer&#xff0c;是阻塞式锁和相关的同步器工具的框架。常用的ReentrantLock、Semaphore、CountDownLatch等都有实现它。 本文参考&#xff1a; 深入理解AbstractQueuedSynchronizer只需…

Haproxy服务

目录 一.haproxy介绍 1.主要特点和功能 2.haproxy 调度算法 3.haproxy 与nginx 和lvs的区别 二.安装 haproxy 服务 1. yum安装 2.第三方rpm 安装 3.编译安装haproxy 三.配置文件详解 1.官方地址配置文件官方帮助文档 2.HAProxy 的配置文件haproxy.cfg由两大部分组成&…

HTML2048小游戏

源代码在效果图后面 效果图 源代码 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>2048 Game&l…

UDP客户端、服务端及简易聊天室实现 —— Java

UDP 协议&#xff08;用户数据包协议&#xff09; UDP 是无连接通信协议&#xff0c;即在数据传输时&#xff0c;数据的发送端和接收端不建立逻辑连接&#xff0c;简单来说&#xff0c;当客户端向接收端发送数据时&#xff0c;客户端不会确认接收端是否存在&#xff0c;就会发出…

使用llama-cpp-python制作api接口

文章目录 概要整体操作流程技术细节小结 概要 使用llama-cpp-python制作api接口&#xff0c;可以接入gradio当中&#xff0c;参考上一节。 llama-cpp-python的github网址 整体操作流程 下载llama-cpp-python。首先判断自己是在CPU的环境下还是GPU的环境下。以下操作均在魔搭…