JAVA——volatile,wait,notife

文章目录

  • volatile关键字
    • 简识jvm内存模型
    • 内存上的优化
    • 问题的产生
    • volatile的作用
  • wait()
    • wait()的作用
  • notify()
    • notify的唤醒顺序

volatile关键字

volatile关键字可以保证内存的可见性,什么是内存的可见性呢?这时候我们需要先想明白一个地方就是内存是可见的吗?我们能直接看到我们写的一个变量或者申请的一块儿内存来自于那里吗?很明显不知道,所以在此我们要先搞明白jvm的内存模型

简识jvm内存模型

在这里插入图片描述
从这张图种我们可以清除的看到其实我们的创建的线程在工作的时候,我们的数据并不是直接加到主内存中的,这里的主内存我们可以理解成硬盘内存,而工作内存其实就是jvm为每个线程独立开辟的一块内存,而线程在工作中所产生的各种变量啊资源啊其实并不是直接加载到主内存中的而是先加载到工作内存中在加载到主内存中,而工作内存其实就是在我们的cpu和寄存器中临时开辟的一块儿内存。
举个例子
我们来举个例子帮助大家理解一下现在假如说我们有一个java线程t1线程,这个t1有一块属于自己的工作内存,那么当我们的t1线程创建一个变量叫做x然后这个x的一个历程应该是先加载到工作内存再由工作内存转存到主内存中,那么当我们突然需要对x进行修改呢?其实就是先把主内存中x读取出来然后交给t1线程进行修改,修改过后再重新更新到主内存中。

我们可以知道工作内存和主内存是独立的,各个工作内存之间也是独立的,当线程创建或者修改某个变量时将会将这个变量传递给工作内存再由工作内存加载到主内存中。

内存上的优化

可是这里面有一个问题就是我们在主内存中读取或者修改某个变量都是非常麻烦的,因此为了避免这些麻烦呢jvm做的有个优化那就是,当jvm发现某个变量短时间内进行了大量的修改或者访问的时候那么jvm就不会把这个变量加载到主内存中而是先把这个变量加载到工作内存中。以此来避免时间的浪费,但是一个问题的解决有时也会诞生新的问题。

问题的产生

当我们是单线程的时候上述优化可以说是非常的优秀,但是如果是多线程呢?比如说下面这个例子

import java.util.Scanner;public class Main {static class Counter {public int flag = 0;}public static void main(String[] args) {Counter c=new Counter();Thread t1=new Thread(()->{while(c.flag==0){}});Scanner scan=new Scanner(System.in);Thread t2=new Thread(()->{System.out.println("输入一个整数:");c.flag=scan.nextInt();});t1.start();t2.start();}
}

对于上面的这个代码我们来看一下运行截图
在这里插入图片描述
请看当我们输入非0的时候代码并没有像我们预想中的那样结束相反一直在运行这是为什么呢?因为我们刚刚说了当一个线程需要使用某个资源的时候会先从主内存中申请而,一个线程改变某个资源的时候也会查看这个资源是否被多次调用如果是的话那就会先暂时不把他存入主内存中,这里t1线程不断的查看当前变量的值导致jvm认为这个变量如果直接加载到内存中会相当的影响程序的效率因此关于这个变量的修改等都会先存入工作内存中,这就导致我们虽然输入了1但是这个1不会被t1线程查询到因为这个值根本没有写入主内存中。那么解决办法就是用volatile。

volatile的作用

它的作用其实就是让被修饰的变量在被修改后直接存入主内存中。

代码被volatile修饰后:

  • 改变线程工作内存中volatile变量副本的值
  • 将改变后的副本的值从工作内存刷新到主内存

代码读取volatile修饰的变量:

  • 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 从工作内存中读取volatile变量的副本

这里我们可以看到volatile的作用其实就是让jvm不要进行所谓的优化,我们对某个变量的修改或者访问将直接让工作内存重新从主内存中获取,从而避免了上述情况的发生。

wait()

由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.
这里给大家举个例子比如说我们在食堂打饭,是要进行排队的此时没有排到的同学需要站在后面进行等待,而对于线程来说由于是抢占式执行因此当我们不管的时候其实更像是下面的那个图片很多人在那里围着抢饭分不出来谁先谁后,谁先抢到谁就能吃上,因此是比较混乱的,在实际开发中我们希望我们的线程调度是可以有一定管理的,尤其是当面对一些突发情况的时候,比如说我们在ATM机排队办理业务,当此时正在办理取钱的业务的线程发现ATM里面没有钱这时候由于他特别没有素质因此抢占着这个ATM机不出来,这时候就会造成后面排队的线程饿死,因此wait()方法的作用这时候就体现出来了那就是把这种线程给暂停掉执行。
在这里插入图片描述
在这里插入图片描述

wait()的作用

关于wait的作用主要分为以下三点

wait做的事情:
使当前正在运行代码的线程进行等待(把线程放进等待队列中)
释放当前的锁
满足一定条件时被唤醒并且重新获取这个锁

这里我们要明白一个事情那就是wait的工作中是会释放其拥有的锁的,因此我们要先保证这个线程已经拥有了锁。

因此wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

wait结束的条件

  • 其他线程调用该对象的 notify 方法.
  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
  • 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出InterruptedException 异常.

那么接下来我们来使用以下notify方法

notify()

notify方法是用来唤醒等待的线程那么他是如何唤醒的呢我们来看一下以下代码

import java.util.Scanner;public class Main {public static void main(String[] args) {Object locker=new Object();Thread t1=new Thread(()->{synchronized (locker){System.out.println("开始等待");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("已被唤醒");}});Thread t2=new Thread(()->{synchronized (locker){System.out.println("开始唤醒");locker.notify();System.out.println("唤醒成功");}});t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}try {t2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

我们来看一下运行截图
在这里插入图片描述
这里我们可以看到当t1线程等待后会暂停代码的执行当t2线程唤醒之后t1线程会继续自己还未完成的代码继续执行,而不是重新执行。
但是这里我们要明白一个问题就是当t1线程被唤醒后是否需要重新获取锁呢?如果需要的话那么这是怎样的一个打印顺序呢?从上面的代码截图可以看出来那就是t2线程此时也获取了lcoker锁因此肯定不可能因为t1现成的唤醒而直接释放自己的锁从而让t1继续运行而是会选择当自己的代码运行完毕后再释放锁让t1继续执行。我们可以用下面的代码进行实验

import java.util.Scanner;public class Main {public static void main(String[] args) {Object locker=new Object();Thread t1=new Thread(()->{synchronized (locker){System.out.println("开始等待");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("已被唤醒");}});Thread t2=new Thread(()->{synchronized (locker){System.out.println("开始唤醒");locker.notify();while(true){synchronized (locker){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("不释放锁");}}}});t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}try {t2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

在这里插入图片描述
这里我们可以看到即使我们唤醒了t1线程但是当我们不释放锁的时候t1线程也不能继续运行。

notify的唤醒顺序

从官方的文档来看notify其实是乱序唤醒的是由JVM内部的调度机制决定的,至于究竟是什么调度机制呢?我也不清楚,但是我们可以思考一个事情就是如果说我们唤醒一个线程后就让notify线程直接释放自己的锁,那么这时候释放的顺序会是什么样子呢?我的运行结果是顺序的大家也可以下去尝试一下。

	努力学习和工作和爱的人并肩看夕阳

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins中支持maven构建遇到仓库报错问题

目的 Jenkins中支持maven构建(Jenkins使用docker安装) 问题 1.构建一个maven项目 2.执行报错 /var/lib/jenkins/local_maven_repo/com/sx/root/1.0.4/root-1.0.4.pom.part.lock (No such file or directory) Failed to transfer Could not transfer artifact co…

Unity发布webgl之后打开PDF文件,不使用js,不和浏览器交互

创建一个按钮,然后点击就会打开 在webgl下要使用这样的路径拼接,不然就会报错。 btnBook.onClick.AddListener(() >{var uri new System.Uri(Path.Combine(Application.streamingAssetsPath "/Books", "文档.pdf"));Debug.Log…

是德科技keysight N1912A双通道功率计

181/2461/8938产品概述: Keysight(原Agilent) N1912A P系列双通道功率计可提供峰值、峰均比、平均功率、上升时间、下降时间、最大功率值、最小功率值以及宽带信号的统计数据。 Keysight(原Agilent) N1912A P系列双通道功率计, 可提供峰值、峰均比、平均功率、上升…

53、Qt/信号与槽、QSS界面设计20240322

一、使用手动连接,将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中,在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中,在槽函数中判断ui界面上输入的账号是否为"admin",密码是…

机器学习 - 训练模型

接着这一篇博客做进一步说明: 机器学习 - 选择模型 为了解决测试和预测之间的差距,可以通过更新 internal parameters, the weights set randomly use nn.Parameter() and bias set randomly use torch.randn(). Much of the time you won’t know what…

二叉树|112.路径总和

力扣题目链接 class Solution { private:bool traversal(TreeNode* cur, int count) {if (!cur->left && !cur->right && count 0) return true; // 遇到叶子节点,并且计数为0if (!cur->left && !cur->right) return false; …

程序员表白

啥?!你说程序员老实,认真工作,根本不会什么表白!那你就错了!(除了我) 那今天我们就来讲一下这几个代码!赶紧复制下来,这些代码肯定有你有用的时候! 1.Python爱心代码 im…

Matlab使用教程(持续更新)

1. Matlab Matlab被广泛的应用在数据分析,汽车仿真,机器人以及医学研究等众多方面。 它可以帮助我们理解研究复杂的系统。 在60年代和70年代,计算机使得科学家和工程师完成了以前不可能进行的计算;但是需要懂得计算机编程。 C…

【Java开发过程中的流程图】

流程图由一系列的图形符号和箭头组成,每个符号代表一个特定的操作或决策。下面是一些常见的流程图符号及其含义: 开始/结束符号(圆形):表示程序的开始和结束点。 过程/操作符号(矩形)&#xff…

【力扣hot100】128.最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输入:nums [100,4,200,1,3,2] 输出:4 解…

PLC与智能制造——蛋糕增大?谁来先行?

PLC的特点 图1 PLC的特点 PLC与智能制造 “中国制造2025”把智能制造作为自动化和信息化深度融合的主攻方向,其支撑在于强大的工业自动化系统,而PLC是工业自动化系统的“大脑”,它不仅可控制机械装备和生产线,还是信息的采集器和…

synchronized和lock的区别

文章目录 synchronized和lock的区别公平锁和非公平锁可重入锁 synchronized和lock的区别 synchronized 是java的一个关键字,源码在 jvm 中,用 c 语言实现,synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁。 Lock 是…

线性代数基础概念和在AI中的应用

基本概念 线性代数是数学的一个分支,专注于向量、向量空间(也称为线性空间)、线性变换和矩阵的研究。这些概念在数据科学、人工智能、工程学和物理学等多个领域都有广泛应用。以下是这些基本概念的详细解释和它们在数据处理和AI中的应用。 …

社区热议!54.8k Star开源项目,GPT-4Free : 让GPT4免费不是梦

Hello,我是Aitrainee,GPT4Free就是最近传得沸沸扬扬的那个GPT4项目。大家都知道,虽然ChatGPT是免费的,但如果你想用到那些功能更强大的大模型,比如GPT-4、gemini-pro、claude,那就只能选择付费了。 但现在&…

C语言——程序拷贝文件

问题如下: 写一个程序拷贝文件: 使用所学文件操作,在当前目录下放一个文件data.txt,写一个程序,将data.txt文件拷贝一份,生成data_copy.txt文件。 基本思路: 打开文件data.txt,读…

【11】工程化

一、为什么需要模块化 当前端工程到达一定规模后,就会出现下面的问题: 全局变量污染 依赖混乱 上面的问题,共同导致了代码文件难以细分 模块化就是为了解决上面两个问题出现的 模块化出现后,我们就可以把臃肿的代码细分到各个小文件中,便于后期维护管理 前端模块化标准…

Flutter 事件传递简单概述、事件冒泡、事件穿透

前言 当前案例 Flutter SDK版本:3.13.2 本文对 事件传递只做 简单概述,主要讲解,事件传递过程中可能遇到的问题解决,比如 事件冒泡、事件穿透; 不是我偷懒,是自认为没有这几位写的详细、仔细&#xff0c…

防火墙在解决方案及典型项目中的应用

防火墙在解决方案及典型项目中的应用 防火墙作为基础安全防护产品,在各种解决方案、业务场景中配套应用,本节给出各类方案资料链接方便查阅。 防火墙在华为网络解决方案中的应用 解决方案 文档 主要应用 CloudFabric云数据中心网解决方案 资料专区…

如何查看局域网内所有的ip和对应的mac地址

1、windows下查看 方法一、 按快捷键“winr”打开运行界面,输入“CMD”回车: 输入以下命令: for /L %i IN (1,1,254) DO ping -w 1 -n 1 192.168.0.%i 其中 192.168.0.%i 部分要使用要查询的网段,比如 192.168.1.%i 192.168.137.%i 172.16.2…

Protege的推理机

最近一直在使用Protege软件,来构建本体和知识图谱。其中还涉及到了如何指定规则进行推理,需要SWRL(使用这个插件能够比较简单创建规则)这个插件,但我一直找不到这个tab在哪里,但是我确信的是一定存在这个插件,因为prot…