volatile,解决内存可见性引起的问题,wait和notify

news2024/11/29 12:33:23

补充:synchronized(务必会读(辛可肉耐子)会写),要搭配一个对象的时候,不一定非要是访问的this成员

 

synchronized(锁对象){ 代码块}

public synchronized static void func(){} 静态方法和具体对象无关,和类有关了

相当于synchronized(类.class)

在synchronized()里面没有必要去纠结里main写普通对象还是类对象之类的,synchronized不关心对象是什么,只关心两个线程是否针对同一个对象加锁

static:相当于加上了一个类属性/类方法


一、 💛

内存可见性引起的问题,如下图,你在线程1设定假如他不是等于0就终止,然后你在线程2中给他把值改变了,让他的循环结束,但是我么可以看到,当前我们输入了一个不是0的数字,然后试图改变它,却改变不了,这就是内存可见性的问题

程序在运行的时候,java编译器和jvm可能会对代码进行优化,程序猿们写代码,然后java编译器把你代码改了,保持原有逻辑不变的情况下提高代码效率——编译器优化后

并且优化的效果特别好:服务器的启动步骤非常复杂,启动一个差不多10分钟左右(但是假如我们吧优化关了,可能1个小时打底)

我们想要知道他是如何优化的,就要先清楚while循环的本质,两个指令

1.load读取内存

2.jcmp(比较,并且跳转,寄存器操作,速度极快)

此时编译器发现,代码反复的,快速的读取同一个内存值,并且这个内存值每次读出来的结果还是一样的,此时编译器决定,直接把load优化掉了,只是第一次执行load,后续并不执行load,直接拿寄存器中的数据进行比较了。

但是在另一个线程修改t2线程会不会执行,什么时候去执行,因此产生了误判,导致虽然最后t2的isQuit改动了,但是t1线程中,并未重复load也就会导致出现上述问题了。 

import java.util.Scanner;

public  class  Demo {
    public  static int isQuit=0;                 //静态变量
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {           //创立一个线程
            while(isQuit==0){
                ;
            }
            System.out.println("t1 结束了");

        }); 
        Thread t2 = new Thread(() -> {         //我们去用t2来改变t1的值
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入isQuit的数字");
            isQuit=scanner.nextInt();    
        });
        t1.start();
        t2.start();


    }
}

volatile(会读会写 (wao(平🐍)里太哦)弥补上述缺口:意思易变的,修饰一个变量之后,编译器就明白,这个变量是一边倒,就不能按照上述方式处理代码(把读操作优化到寄存器中),让编译器禁止优化,于是保证t1在循环的过程中,始终都能读取内存中的数据

volatile本质是保持变量的内存可见性

见下面代码用法:

下面得到的结果就是正确的,我就暂时省略结果了。

import java.util.Scanner;

public  class  Demo {
    public volatile static int isQuit=0;          //变量static前面
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while(isQuit==0){
                ;
            }
            System.out.println("t1 结束了");

        });
        Thread t2 = new Thread(() -> {
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入isQuit的数字");
            isQuit=scanner.nextInt();
        });
        t1.start();
        t2.start();


    }
}

编译器的优化是一个“玄学问题”,就比如说,我在新代码里面,写了个sleep,同时把volatile取消了,但是这样也正确

可以理解为,加上sleep之后,sleep就会大幅度的影响到while循环的速度,速度慢了,编译器也不打算继续优化了~此时即使不加volatile,也能够及时感知到内存变化了,sleep到底间隔多久,会触发优化,只有那些码届远古大能才知道。 

二、💜 

另一种解释方式

Java的内存模型(JMM)

其实这个理解和上面那个编译器解释原理是一样的,从主内存读,但反复读都是一样的,所以直接就去工作内存读,但是工作内存又是什么鸟东西捏?

工作内存:不是我们平时说的内存,而是cpu的寄存器和cpu缓存统称为工作内存,有人可能会好奇那为啥不叫cpu寄存器+cpu缓存呢(猜:JAVA宣称是跨平台,但是cpu的话又是有一部分硬件知识,他们希望java程序猿可以不用掌握太多的硬件知识。

 内存可见性和加锁描述了线程安全问题的典型情况和处理方式。


三、 💙 

wait(等待)和notify(通知):用来协调线程顺序的重要工具,多线程调度是随机的~很多时候希望多个线程按照咱们规定的顺序来执行,完成线程之间的配合工作。

上面两个都是object提供的方法,也就是说任意对象,当wait引起线程阻塞之后,可以用interrupt方法,把线程给唤醒,打断当前线程的阻塞状态的。

wait执行程序的时候会干三件事:

1.解锁

2.阻塞等待

3.当被其他线程唤醒之后,就会尝试重新加锁,加锁成功,wait执行完毕,继续往下执行其他逻辑。

也就是说wait(需要先加锁)

核心思路:先加锁,在synchronized里面进行wait(加锁要加同一个对象上面),这里的线程会一直阻塞到其他线程notify。

其中最典型的场景就是有效的避免线程饥饿/线程饿汉

 

几个注意

1.要想notify能顺利唤醒wait,就需要确保wait和notify都是同一个对象调用的,

2.wait和notify都需要放到synchronzied之中的,虽然notify不涉及到解锁操作

3.如果进行notify的时候,另一个线程没有处于wait状态,此时notify也没有任何副作用。

t2可以理解成辅助t1的线程,使用notify线程对其他线程统筹安排作用。

import java.util.Scanner;

public  class  Demo {
    public  static Object locker=new Object();
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while(true){
               synchronized (locker) {
                   System.out.println("t1 开启");       //第一步执行的
                   try {
                       locker.wait();                  //第二步执行,t2陷入阻塞状态
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println("t1 over");      //第五步,t1被唤醒
               }
               }
        });
        Thread t2 = new Thread(() -> {
         while (true) {
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             synchronized (locker) {
                 System.out.println("t2 开启");      //第三步执行的
                 locker.notify();                   //第四部执行,唤醒t1
                 System.out.println("t2 over");
             }
         }
        });
        t1.start();
        t2.start();


    }
}

 

线程可能有多个~

几个线程wait,一个线程复制notify,notify只会唤醒一个线程,具体哪个随机,notifyAll会唤醒全部线程(但是不推荐去用),这种也是全随机,就和wait的初心违背了。

如果要唤醒某个特定的线程,就要让不同的线程,使用不同的对象来进行wait,想要唤醒谁,就可以使用对应的对象notify

wait和sleep的区别

sleep有明确的使用是假,到达时间自动被唤醒,也能提前用interrupt

wait:死等,一直等到其他线程notify(但是,是正常唤醒,可继续工作,还会进入wait 状态),wait也可以被interrupt提前唤醒(但是这个是来通知线程要结束了,线程要收尾了)

,当然他也有带时间版的和join差不多,因此协调多个线程执行顺序wait比notify更牛一些

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

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

相关文章

JS+CSS实现内凹导航栏

在移动互联网时代,导航栏是一个非常重要的元素,它能够帮助用户快速找到所需的信息。下面使用JS CSS实现一个内凹导航栏,内凹导航栏则是一种比较流行的设计风格,它能够让导航栏看起来更加立体和美观,视觉效果也非常不错…

vue : 无法加载文件 C:\Users\…\npm\vue.ps1,因为在此系统上禁止运行脚本。

在 PowerShell 中创建 vue 项目时,出现了以下错误导致创建失败:vue : 无法加载文件 C:\Users\…\npm\vue.ps1,因为在此系统上禁止运行脚本。 报错原因 用户权限不足导致无法加载文件,以管理员身份运行终端或者 PowerShell 也可…

使用Three.js制作一个旋转多面体

之前一直对three.js比较好奇,但是一直没有着手学习。今天刷到一篇博客(博主:1_bit),觉得挺有意思,就跟着敲了一下。 html: 其中canvas用于添加渲染好的元素,本篇文章通过CDN形式引入three.js,…

2023 Gartner RPA魔力象限报告解读:国产厂商“破纪录”跃升意味着什么?

2023 Gartner RPA魔力象限报告解读:象限跃升彰显国产RPA厂商实力 2023 Gartner RPA魔力象限报告四大行业趋势,国产RPA厂商已在践行 文/王吉伟 8月3日,全球著名咨询调查机构Gartner发布了《2023年全球RPA魔力象限(Gartner RPA M…

ceph相关概念和部署

Ceph 可用于向云提供 Ceph 对象存储 平台和 Ceph 可用于提供 Ceph 块设备服务 到云平台。Ceph 可用于部署 Ceph 文件 系统。所有 Ceph 存储集群部署都从设置 每个 Ceph 节点,然后设置网络。 Ceph 存储集群需要满足以下条件:至少一个 Ceph 监控器&#x…

10分钟学会阿里OSS对象存储

一. 前言 最近有很多小伙伴问,如果我们要进行大规模的文件存储该怎么做? 其实实现文件存储的技术有很多,如果我们在网上搜索一下,你会发现实现的技术简直是五花八门,比如有一种技术叫FastDFS就可以实现文件存储,但该…

Effective Java笔记(31)利用有限制通配符来提升 API 的灵活性

参数化类型是不变的&#xff08; invariant &#xff09; 。 换句话说&#xff0c;对于任何两个截然不同的类型 Typel 和 Type2 而言&#xff0c; List<Type1 &#xff1e;既不是 List<Type 2 &#xff1e; 的子类型&#xff0c;也不是它的超类型 。虽然 L ist<String…

问道管理:零基础学炒股?

跟着股市的不断升温&#xff0c;越来越多的人参加到了炒股大军中&#xff0c;希望经过股市赚到更多的金钱。但是关于零根底的新手来说&#xff0c;怎么开始学习炒股成为了一个难题。那么&#xff0c;零根底学炒股真的难吗&#xff1f;怎么更好的入门和学习&#xff1f; 首要&am…

线性代数(三) 线性方程组

前言 如何利用行列式&#xff0c;矩阵求解线性方程组。 线性方程组的相关概念 用矩阵方程表示 齐次线性方程组&#xff1a;Ax0&#xff1b;非齐次线性方程组&#xff1a;Axb. 可以理解 齐次线性方程组 是特殊的 非齐次线性方程组 如何判断线性方程组的解 其中R(A)表示矩阵A的…

模型、策略和算法

模型(model)、策略(strategy)和算法(algorithm)是统计学习的所有内容. 模型是统计学习的最终结果&#xff0c;即决策函数(decision function) 或条件概率函数 &#xff0c;它被⽤来预测特定问题下&#xff0c;将来未知输⼊的输出结果. 策略是统计学习过程中的产⽣最优模型的评…

如何使用vue创建一个项目

1、安装node.js 打开cmd 输入node -v 和npm -v查看电脑是否已经安装了node,js 如果出现以下截图&#xff0c;则说明已经有node.js&#xff0c;可忽略此步 如果没有&#xff0c;则打开官网&#xff0c;下载即可 下载地址&#xff1a;Node.js 下载完成后&#xff0c;打开cmd输…

漏洞挖掘日记1:企业src某系统存在登录绕过漏洞

&#xff08;一&#xff09;漏洞描述 这个漏洞属于逻辑漏洞&#xff0c;逻辑漏洞是指开发者在开发过程中&#xff0c;实现业务上出现了逻辑上的漏洞。之所以出现逻辑漏洞&#xff0c;是因为一些开发者&#xff0c;在开发过程中&#xff0c;第一考虑是怎么实现功能&#xff0c;…

OPENCV C++(十)gramm矫正+直方图均衡化

两者都是只对单通道使用&#xff0c;对多通道的话 就需要分离通道处理再合并通道 两种方法&#xff0c;第一个要运算次数太多了&#xff0c;第二个只需要查表 伽马矫正函数&#xff0c;这里用第二种方法&#xff0c;且写法有点高级 int gammaCorrection(cv::Mat srcMat, cv::…

路由器和交换机的区别

交换机和路由器的区别 交换机实现局域网内点对点通信&#xff0c;路由器实现收集发散&#xff0c;相当于一个猎头实现的中介的功能 路由器属于网络层&#xff0c;可以处理TCP/IP协议&#xff0c;通过IP地址寻址&#xff1b;交换机属于中继层&#xff0c;通过MAC地址寻址(列表)…

聚观早报 | 真我GT5系列工艺细节曝光;小米MIXFold3定妆照抢先看

【聚观365】8月10日消息 真我GT5系列工艺细节曝光小米MIX Fold 3定妆照抢先看360智脑整体能力提升15%科大讯飞智能办公本X3正式发布索尼第二季度营收增长33% 真我GT5系列工艺细节曝光 在此前举办的ChinaJoy2023上&#xff0c;作为手机行业新势力的真我realme携手旗下多款爆款…

JavaWeb 速通Session

目录 一、Session的引入 1.什么是Session&#xff1f; 2.Session的基本原理 : 3.Session长什么样子&#xff1f; 二、Session的底层机制 1. Session接口相关的方法 : 1 req.getSession() : 2 httpSession.setAttribute(String name, Object val); 3 Object obj httpSessi…

Python(七十五--总结)列表、字典、元组、集合总结

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

【Python篇】Python基础语法

【Python篇】Python基础语法 拖拖拖&#xff0c;能使工作便捷高效的为何要拒绝&#xff0c;作个记录—【蘇小沐】 文章目录 【Python篇】Python基础语法1.实验环境 1、标识符2、Python保留字&#xff08;关键字&#xff1a;不能用作任何标识符名称&#xff09;3、注释1&#x…

定制全彩LED电子屏的模组类型

定制全彩LED电子屏的成功与否很大程度上取决于选择合适的LED模组。LED模组是LED电子屏的基本组成部分&#xff0c;直接影响显示效果、可靠性和性能。什么是LED模组&#xff1f;根据LED屏表面处理的工艺不同&#xff0c;我们可以把其分为插件模组、表贴模组、亚表贴模组、三合一…

Redis 搭建哨兵集群

文章目录 0. 哨兵原理1. 哨兵集群架构2. 准备实例和配置3. 启动4. 测试5. RedisTemplate 的哨兵模式配置地址配置读写分离 在 主从架构 Redis 搭建主从集群 中&#xff0c;一个 slave 节点挂了无影响&#xff0c;但是 master 节点挂了&#xff0c;就无法进行写操作了&#xff0…