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

多线程的学习中篇下

请添加图片描述

volatile 关键字

volatile 能保证内存可见性
volatile 修饰的变量, 能够保证 “内存可见性”

示例代码:

image-20230927234356426

运行结果:
image-20230927234904570

当输入1(1是非O)的时候,但是t1这个线程并沿有结束循环,
同时可以看到,t2这个线程已经执行完了,而t1线程还在继续循环.

这个情况,就叫做内存可见性问题 ~~ 这也是一个线程不安全问题(一个线程读,一个线程改)

while (myCounter.flag == 0) { // 循环体空着,什么也不做 }

这里使用汇编来理解,大概就是两步操作:

  1. load, 把内存中 flag 的值,读取到寄存器里.
  2. cmp, 把寄存器的值,和0进行比较,根据比较结果,决定下一步往哪个地方执行(条件跳转指令).

上述是个循环,这个循环执行速度极快,一秒钟执行百万次以上…
循环执行这么多次,在线程 t2 真正修改之前, load得到的结果都是一样的;
另一方面, load操作和cmp操作相比,速度慢非常非常多!!!

注:
CPU针对寄存器的操作,要比内存操作快很多,快3-4数量级;
计算机对于内存的操作,比硬盘快3-4个数量级.

由于 load 执行速度太慢(相比于cmp来说),再加上反复 load 到的结果都一样, JVM 就做出了一个非常大胆的决定 ~~ 判定好像没人改 flag 值,不再真正的重复 load 了,干脆就只读取一次就好了 => 编译器优化的一种方式.
实际上是有人在修改的,但是 JVM/编译器 对于这种多线程的情况,判定可能存在误差.
此时,就需要我们手动干预了,可以给 flag 这个变量加上 volatile 关键字,意思就是告诉编译器,这个变量是"易变"的,要每次都重新读取这个变量的内存内容,不能再进行激进的优化了.
博主感慨: 快和准之间往往不可兼得

内存可见性问题

一个线程针对一个变量进行读取操作,同时另一个线程针对这个变量进行修改,此时读到的值,不一定是修改之后的值;这个读线程没有感知到变量的变化;
归根结底是 编译器/JVM 在多线程环境下优化时产生了误判了.
备注:
(1)上述说的内存可见性编译器优化的问题,也不是始终会出现的(编译器可能存在误判,也不是100%就误判!);
(2)编译器的优化,很多时候是“玄学问题”,应用程序这个角度是无法感知的.编译器的优化,很多时候是“玄学问题”,应用程序这个角度是无法感知的.

代码改正:

class MyCounter {volatile public int flag = 0;
}

运行结果:

image-20230928004924362

注意事项:
(1) volatile 只能修饰变量;
(2) volatile 不能修饰方法的局部变量,局部变量只能在你当前线程里面用,不能多线程之间同时读取/修改(天然就规避了线程安全问题);

(1)局部变量只能在当前方法里使用的,出了方法变量就没了,方法内部的变量在"栈”这样的内存空间上;
(2)每个线程都有自己的栈空间,即使是同一个方法,在多个线程中被调用,这里的局部变量也会处在不同的栈空间中,本质上是不同变量,也就涉及不到修改/读取同一个变量的操作;
(3)栈记录了方法之间的调用关系;
个人理解: 局部变量只对当前线程可见,其他线程看不了.

(3) 如果一个变量在两个线程中,一个读,一个写,就需要考虑volatile 了;
(4) volatile 不保证原子性,原子性是靠 synchronized 来保证的. synchronized 和 volatile 都能保证线程安全 => 不能使用 volatile 处理两个线程并发++这样的问题;
(5) 如果涉及到某个代码,既需要考虑原子性,有需要考虑内存可见性,就把 synchronized 和 volatile 都用上就行了.


从 JMM 的角度重新表述内存可见性问题

内存可见性问题,其他的一些资料,谈到了JMM(Java Memory Mode ~~ Java内存模型)
从 JMM 的角度重新表述内存可见性问题(Java的官方文档的大概表述):
Java 程序里,主内存,每个线程还有自己的工作内存(线程 t1 的和线程 t2 的工作内存不是同一个东西);
线程 t1 进行读取的时候,只是读取了工作内存的值;
线程 t2进行修改的时候,先修改的工作内存的值,然后再把工作内存的内容同步到主内存中,但是由于编译器优化,导致线程 t1没有重新的从主内存同步数据到工作内存,读到的结果就是“修改之前"的结果.

如果把"主内存”代替成"内存",把“工作内存"代替成"CPU寄存器",就容易理解.
注: 之所以上面这段话这么晦涩,是翻译不行,翻译官得背锅 ~~ 翻译的结果让人误会了!!!
主内存: main memory => 主存,也就是平时所说的内存
工作内存: work memory =>工作存储区,并非是所说的内存,而是CPU上存储数据的单元(寄存器)

为什么Java这里,不直接叫做“CPU寄存器",而是专门搞了"工作内存”说法呢?

这里的工作内存,不一定只是CPU的寄存器,还可能包括CPU的缓存cache.

image-20230928013620585

当CPU要读取一个内存数据的时候,可能是直接读内存也可能是读cache还能是读寄存器…
引入cache之后,硬件结构就更复杂了,工作内存(工作存储区): CPU寄存器 + CPU的cache;
一方面是为了表述简单,另一方面也是为了避免涉及到硬件的细节和差异,Java里就使用"工作内存"这个词来统称(泛指)了;毕竟,现实中有的 CPU 可能没有 cache, 有的 CPU 有;有的 CPU 可能有一个cache,还可能有多个;现代的 CPU 普遍是3级cache, L1, L2, L3,总之,情况多样.
注: 学校的"计算机系统结构”会讲解CPU内部的结构,尤其是寄存器, cache,指令等等,上这门课的时候要好好听讲.

wait 和 notify

线程最大的问题,是抢占式执行,随机调度~~
程序猿写代码,不喜欢随机,喜欢确定的东西,于是发明了一些办法,来控制线程之间的执行顺序,虽然线程在内核里的调度是随机的,但是可以通过一些 API 让线程主动塞,主动放弃CPU(给别的线程让路).
比如,t1,t2 两个线程,希望t1先干活,干的差不多了,再让t2来干.就可以让t2先wait(阻塞,主动放弃CPU)等t1干的差不多了,再通过notify 通知t2,把t2唤醒,让t2接着干.

那么上述场景,使用 join 或者 sleep行不行呢?

使用join,则必须要t1彻底执行完,t2才能运行.如果是希望t1先干50%的活,就让t2开始行动,join无能为力.
使用sleep,指定一个休眠时间的,但是t1执行的这些活,到底花了多少时间,不好估计.
使用wait和notify可以更好的解决上述的问题.

注: wait, notify, notifyAll 这几个类,都是Object类(Java里所有类的祖宗)的方法.Java里随便new个对象,都可以有这三个方法!!

wait

wait 进行阻塞.某个线程调用wait方法,就会进入阻塞(无论是通过哪个对象 wait的),此时就处在WAITING状态.
wait 不加任何参数,就是一个"死等"一直等待,直到有其它线程唤醒它.
示例代码:

public class ThreadDemo16 {public static void main(String[] args) throws InterruptedException {Object object = new Object();object.wait();}
}

throws InterruptedException : 这个异常,很多带有阻塞功能的方法都带.这些方法都是可以 interrupt 方法通过这个异常给唤醒的.

运行结果:

image-20230927204141554

IllegalMonitorStateException
~~ 非法的锁状态异常
~~ 锁的状态,无非就是被加锁的状态和和被解锁的状态.

为什么有这个异常,要先理解 wait 的操作是干什么了.
1.先释放锁
2.进行阻塞等待
3.收到通知之后,重新尝试获取锁,并且在获取锁后,继续往下执行.

这里锁状态异常,就是没加锁呢,就想着释放锁.就好比单身着呢,就想着分手.

public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println("wait 之前");object.wait();System.out.println("wait 之后");}}

image-20230927205029017

虽然这里wait是阻塞了,阻塞在 synchronized 代码块里,实际上,这里的阻塞是释放了锁的,此时其他线程是可以获取到object这个对象的锁的,此时这里的阻塞,就处在WAITING状态.

image-20230928094946950

t1.start();
t2.start();

如果代码这里写作 t1.start 和 t2.start 由于线程调度的不确定性,此时不能保证一定是先执行 wait ,后执行notify. 如果调用notify,此时没有线程wait,此处的wait是无法被唤醒的!!!(这种通知就是无效通知).
因此此处的代码还是要尽量保证先执行wait后执行notify才是有意义的.

改正的代码:

   public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread t1 = new Thread(() -> {// 这个线程负责进行等待System.out.println("t1: wait 之前");try {synchronized (object) {object.wait();}} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2: wait 之后");});Thread t2 = new Thread(() -> {System.out.println("t2: notify 之前");synchronized (object) {// notify 务必要获取到锁,才能进行通知object.notify();}System.out.println("t2: notify 之后");});t1.start();// 此处写的 sleep 500 是大概率会让 t1 先执行 wait 的// 极端情况下,电脑特别卡的时候, 可能线程的调度时间就超过了 500 ms// 还是可能 t2 先执行 notifyThread.sleep(500);t2.start();}

运行结果:

image-20230927214510375

此处,先执行了wait,很明显wait操作阻塞了,没有看到wait之后的打印;
接下来执行到了t2, t2进行了notify的时候,才会把t1的wait唤醒.t1才能继续执行.
只要t2不进行notify,此时t1就会始终wait下去(死等).

wait无参数版本,就是死等的.
wait带参数版本,指定了等待的最大时间.

wait的带有等待时间的版本,看起来就和sleep有点像.其实还是有本质差别的:
虽然都是能指定等待时间,也都能被提前唤醒(wait是使用notify 唤醒, sleep使用interrupt唤醒)但是这里表示的含义截然不同.
notify唤醒wait,这是不会有任何异常的.(正常的业务逻辑),interrupt唤醒sleep 则是出异常了(表示一个出问题了的逻辑).

如果当前有多个线程在等待object对象,此时有一个线程 object.notify(),此时是随机唤醒一个等待的线程.(不知道具体是哪个),但是,可以用多组不同的对象来控制线程的执行顺序.
比如,有三个线程,希望先执行线程1,再执行线程2,再执行线程3,
创建obj1,供线程1,2使用创建obj2,供线程2,3使用线程3, obj2.wait
线程2.obj1.wait(),唤醒之后执行obj2.notify()
线程1执行自己的任务,执行完了之后,obj1.notify即可.

notifyAll和notify非常相似.
多个线程 wait 的时候, notify随机唤醒一个, notifyAll 所有线程都唤醒,这些线程再一起竞争锁…

相关文章:

多线程的学习中篇下

volatile 关键字 volatile 能保证内存可见性 volatile 修饰的变量, 能够保证 “内存可见性” 示例代码: 运行结果: 当输入1(1是非O)的时候,但是t1这个线程并沿有结束循环, 同时可以看到,t2这个线程已经执行完了,而t1线程还在继续循环. 这个情况,就叫做内存可见性问题 ~~ 这…...

贪心算法-拼接字符串使得字典顺序最小问题

题目1 给定一个由字符串组成的数组strs,必须把所有字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果 思路:对数组排序,排序规则是对ab和ba的字符串进行比较大小,返回较小的顺序放到数组中最后将…...

Linux--互斥锁

一、与互斥锁相关api **互斥量(mutex)**从本质上来说是一把锁。在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量。对互斥量进行枷锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释…...

[2023.09.21]:源码已上传,供大家了解Rust Yew的前后端开发

这个资源是Rust的源代码压缩包,供大家了解Rust Yew的前后端开发。 资源中的代码非常简洁易懂,虽然离商用场景还有一段距离,但是涵盖了前端的组件搭建、事件通信和反向代理,以及后端的Restful API的路由、功能实现和数据库访问。此…...

时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解

时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解 目录 时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现CEEMD互补集合经验模态分解时间序列信号分解 1.分解效果图 &#xff0…...

linux缓存-利用缓存提高性能的编程技巧

目录 利用缓存提高性能的编程技巧 实现方式 利用缓存提高性能的编程技巧 利用GCC编译器对齐属性 __attribute__((__aligned__(n))),利用处理器的缓存提高程序的执行速度; 使变量的起始地址对齐到一级缓存行长度的整数倍;使结构体对齐到一级缓存行长度…...

Socks5代理、IP代理与其在爬虫开发中的应用

在当今数字化时代,网络安全和数据获取变得愈发重要。代理服务器作为一种关键的技术手段,为网络工程师和爬虫开发人员提供了有力的工具。本文将深入探讨Socks5代理、IP代理以及它们在网络安全和爬虫应用中的角色与意义。 1. 代理服务器简介 代理服务器是…...

【C++】C++继承——切片、隐藏、默认成员函数、菱形

​ ​📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:C学习 🎯长路漫漫浩浩,万事皆有期待 上一篇博客:【C】STL…...

WebGL笔记:WebGL中绘制圆点,设定透明度,渲染动画

WebGL 绘制圆点 基于片元着色器来画圆形片元着色器在屏幕中画图是基于一个个的像素的每次画一个像素时,都会执行片元着色器中的main方法那么,我们就可以从这一堆片元中(n个像素点)找出属于圆形的部分片元的位置叫做 gl_PointCoord (一个点中片元的坐标位…...

华为云云耀云服务器L实例评测 | 实例使用教学之简单使用:通过命令行管理华为云云耀云服务器

华为云云耀云服务器L实例评测 | 实例使用教学之简单使用:通过命令行管理华为云云耀云服务器 介绍华为云云耀云服务器 华为云云耀云服务器 (目前已经全新升级为 华为云云耀云服务器L实例) 华为云云耀云服务器是什么华为云云耀云服务…...

微信小程序 课程签到系统

目录 前端页面展示主页面我的课程个人中心评论功能签到功能课程绑定超级管理员页面 前端文件结构文件结构app.json前端架构和开发工具前端项目地址 后端后端架构后端项目地址 注意事项 前端页面展示 主页面 登录页面: 账号是:用户名或者手机号 密码是&a…...

如何用Postman做接口自动化测试

前言 什么是自动化测试 把人对软件的测试行为转化为由机器执行测试行为的一种实践。 例如GUI自动化测试,模拟人去操作软件界面,把人从简单重复的劳动中解放出来。 本质是用代码去测试另一段代码,属于一种软件开发工作,已经开发完成…...

支付宝电脑网站支付,异步通知

一:异步通知是支付宝回调商户的服务器,所以这个地址需要通过外网访问,在真实项目中都会有对应的服务器,但是在测试中只有使用内网穿透工具 推荐使用NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 配置好内网穿透之后不要忘记…...

【广州华锐互动】奶牛养殖难产助产3D沉浸式教学平台

在传统的奶牛难产助产教学中,主要依赖理论知识和2D图像来进行教学。然而,这种教学方式往往无法全面、真实地展示奶牛难产的各种情况,教学效果也不尽如人意。随着科技的发展,3D互动教学的出现,为奶牛难产助产教学带来了…...

IDEA社区版,真香!

IDEA(IntelliJ IDEA)是众多 Java 开发者的首选。 商业版的昂贵 IDEA 商业版(IntelliJ IDEA Ultimate)功能非常强大,能够满足 Java 开发的所有需求,但其高昂的价格…… 此时只能感叹,不是不想用…...

SpringBoot实现全局异常处理

1.全局异常处理介绍 1.1 简介 全局异常处理器即把错误异常统一处理的方法,可以在多个地方使用,而不需要为每个地方编写单独的处理逻辑。它可以帮助开发人员更好地管理异常,并提供一致的错误处理方式。 1.2 优点 1.全局异常处理可以提高代码…...

Day05-循环高级和数组

循环高级 1.无限循环 概念: 又叫死循环。循环一直停不下来。 for格式: for(;;){System.out.println("循环执行一直在打印内容"); } 解释: 初始化语句可以空着不写,表示循环之前不定义任何的控制变量。 条件判断…...

从代码操作层面解释什么是“面相对象编程”?

起因: 今天开了一个小会,会上朋友给我们说了一个事,Java项目上他开发一个小功能 用了很多代码,项目经理发现代码太多,说要优化一下,然后亲自帮同事优化,结果是查库的代码少了至少10条sql&#x…...

【MySQL】SQL优化、char、varchar、外键约束、排查慢sql等重点知识汇总

目录 SQL语句 char和varchar比较 SQL语句如何优化 说一下你理解的外键约束 如何排查慢 sql SQL语句 对库操作 创建数据库 create database 数据库名 删除数据库 drop database 数据库名 显示所有数据库 show databases 选中数据库 use 数据库名 对表操作 创建表…...

git管理常用命令

1、下载代码 git clone 地址2、软件代码提交 1、查看工程中被修改的文件&#xff1a;git status 2.将不需要提交的文件回退&#xff1a;git check <文件路径> 3.更新工程到最新&#xff1a;git pull 4.将本地代码添加到暂存区&#xff1a;git add <将要提交的文件路…...

Python 逢七拍手小游戏2.0

"""逢七拍手游戏介绍&#xff1a;逢七拍手游戏的规则是&#xff1a;从1开始顺序数数&#xff0c;数到有7&#xff0c;或者是7的倍数时&#xff0c;就拍一手。例如&#xff1a;7、14、17......70......知识点&#xff1a;1、循环语句for2、嵌套条件语句if/elif/e…...

基于微信小程序的在线小说阅读系统,附数据库、教程

1 功能简介 Java基于微信小程序的在线小说阅读系统 微信小程序的在线小说阅读系统&#xff0c;系统的整体功能需求分为两部分&#xff0c;第一部分主要是后台的功能&#xff0c;后台功能主要有小说信息管理、注册用户管理、系统系统等功能。微信小程序主要分为首页、分类和我的…...

216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。 示例 1: 输入: k 3, n 7 输出: [[1,2,4]] 解释: …...

【Java】数组的深浅拷贝问题(二维数组举例)(136)

深拷贝和浅拷贝&#xff1a; 对于数组来说&#xff0c;深拷贝就是相当于拷贝了数组的对象&#xff08;基本数据类型&#xff09;&#xff0c;也就是数组当中的内容。而浅拷贝就是拷贝的是数组的地址&#xff08;引用类型&#xff09;&#xff0c;浅拷贝只是复制了对象的引用地…...

【轮趣-科大讯飞】M260C 环形六麦测试 2 - ROS1功能测试与唤醒、语音识别程序解析

所有内容请看&#xff1a; 博客学习目录_Howe_xixi的博客-CSDN博客https://blog.csdn.net/weixin_44362628/article/details/126020573?spm1001.2014.3001.5502原文在飞书&#xff0c;请联系我获取阅读链接&#xff0c;我太懒了...

油猴(篡改猴)学习记录

第一个Hello World 注意点:默认只匹配了http网站,如果需要https网站,需要自己添加match https://*/*代码如下 这样子访问任意网站就可以输出Hello World // UserScript // name 第一个脚本 // namespace http://tampermonkey.net/ // version 0.1 // descri…...

LeetCode 面试题 05.08. 绘制直线

文章目录 一、题目二、Java 题解 一、题目 已知一个由像素点组成的单色屏幕&#xff0c;每行均有 w 个像素点&#xff0c;所有像素点初始为 0&#xff0c;左上角位置为 (0,0)。 现将每行的像素点按照「每 32 个像素点」为一组存放在一个 int 中&#xff0c;再依次存入长度为 le…...

机器人中的数值优化|【六】线性共轭梯度法,牛顿共轭梯度法

机器人中的数值优化|【六】线性共轭梯度法&#xff0c;牛顿共轭梯度法 往期回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法&#xff0c;可行牛顿法的python实现&#xff0c;以Rosenbrock function为例 机器人中的数值优化|【三】无约束优化…...

FastestDet---原理介绍

1.测试指标 2.算法定位 FastestDet是设计用来接替yolo-fastest系列算法,相比于业界已有的轻量级目标检测算法如yolov5n, yolox-nano, nanoDet, pp-yolo-tiny, FastestDet和这些算法根本不是一个量级,FastestDet无论在速度还是参数量上,都是要小好几个数量级的,但是精度自然…...

ORACLE 在内存管理机制上的演变和进化

截止目前&#xff0c;计算机内存仍然被认为是我们可以获得的最快速度的物理存储设备。 将频繁访问的数据尽可能地置于内存中&#xff0c;已成为当前各种软件和应用程序提高数据访问性能&#xff0c;减少访问延迟的最为有效的途径。 然而&#xff0c;内存作为关键的计算资源&am…...

芜湖酒店网站建设/枸橼酸西地那非片的作用及功效

1. 先给手机刷root权限&#xff0c;执行命令&#xff1a;adb root adb remountok后&#xff1a;把tcpdump放到c盘根目录下&#xff1a;C:\2. 执行命令&#xff1a;adb push c:/tcpdump /data/local/tcpdump&#xff08;这个命令是把tcpdump拷到手机中去 &#xff09;3. adb sh…...

广州做网站的企业/外链工厂 外链

题意:给定函数\(f(x)\),有\(n^2-3*n2\sum_{d|n}f(d)\),求\(\sum_{i1}^nf(i)\) 题解:很显然的杜教筛,假设\(g(n)n^2-3*n2\),那么有\(gf*I\),由莫比乌斯反演,\(fg*\mu\),可以O(nlogn)预处理到1e6,剩余部分杜教筛 我们先观察杜教筛的推导过程,假设要求\(s(n)\sum_{i1}^nf(i)\),\(\…...

南水北调建设管理局网站/seo电商运营是什么意思

曾有人问我&#xff0c;为什么要去干解析 dex 文件这种麻烦的事?我想说的是写个解析脚本不是为了模仿着 apktools 造轮子&#xff0c;而是在解析过程中寻找逆向的道路&#xff0c;方法会变&#xff0c;工具会变&#xff0c;但一切都建立在 dex 上的安卓不会变一、什么是 Dex 文…...

乳山网站开发/网上营销策略有哪些

费了好大的劲终于成功了终于注册成功GEE了&#xff0c;下面把注册过程记录一下。 GEE注册过程一、注册Google账号1.打开GEE注册页面&#xff1a;Google Earth Engine点击右上角Sig up。2.创建用户3.输入手机号验证4.添加辅助邮箱二、GEE申请使用1.点击进入GEE官网&#xff1a;h…...

食品网站建设策划/谷歌三件套一键安装

当我进行Python编程时&#xff0c;我总是使用制表符进行缩进。 但后来我在SO上遇到了一个问题&#xff0c;有人指出大多数Python程序员使用空格而不是制表符来最小化编辑器到编辑器的错误。这有什么不同&#xff1f; 还有其他原因可以使用空格而不是Python的制表符吗&#xff1…...

怎么提高网站的权重/友链申请

IDEA整合GitLab&#xff0c;并进行分支使用一&#xff1a;IDEA安装GitLab插件二&#xff1a;检出项目三&#xff1a;将总分支代码更新到自己的分支中总结步骤3.1介绍3.2 切换总分支&#xff0c;更新代码3.3 将最新代码合并到自己的分支中3.4 push当前master分支的代码到自己的远…...