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.mzph.cn/news/766016.shtml

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

相关文章

二叉树的链式结构和遍历(下)

又见面了,小伙伴们。今天我们继续来学习二叉树,今天的内容相对来说比较容易理解,前提是需要你们自己动手画图才会好理解。眼过千遍不如手过一遍。所以小伙伴们要多动手哦。直接开始今天的学习吧 1.二叉树链式结构的实现 1.1 前置说明 在学习…

如何成为顶尖程序员?

如何成为顶尖程序员? 程序员是一种特殊的职业,但为什么大多数程序员无法达到顶尖水平?本文探讨了几个可能的原因,包括缺乏热情和动力、基础和原理的不足、实践和经验的匮乏,以及思考和创新的欠缺。了解这些原因可以帮助…

基于SpringBoot+MyBatis+Vue的电商智慧仓储管理系统的设计与实现(源码+LW+部署+讲解)

前言 博主简介👨🏼‍⚕️:国内某一线互联网公司全栈工程师👨🏼‍💻,业余自媒体创作者💻,CSDN博客专家🏆,Java领域优质创作者📕&#x…

Redis中文乱码问题

最近排查问题,发现之前的开发将日志写在redis缓存中(不建议这样做),我在查看日志的时候发现没办法阅读,详细是这样的: 查阅资料后发现是进制问题,解决方法是启动客户端的时候将redis-cli改为red…

【go从入门到精通】if else 条件控制

作者简介: 高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C,go等语言开发经验,mysql,mongo,redis等数据库,设计模…

电脑安装双系统windows和ubuntu server

1.创建Ubuntu-server的启动盘 首先要从官网下载Ubuntu-server18.04的ISO文件,用rufs烧录到U盘。如下所示 2. 磁盘分区 在windows创建两个盘(linuxboot 和linuxroot),后面一个一个用于boot,一个用于root. 3.开机U盘启…

Flutter学习10 - Json解析与Model使用

对于网络请求返回的 Json 数据&#xff0c;一般会进行如下解析&#xff1a; 将 Json String 解析为 Map<String, dynamic>将 Json String 解析为 Dart Model 发起一个返回 Json String 的网络请求 import package:http/http.dart as http;void main() {_doGet(); }_do…

用好商用无人自助咖啡机,真正实现“AI智能”制饮!

随着科技的不断进步和智能化技术的广泛应用&#xff0c;商用无人自助咖啡机作为餐饮行业的新宠&#xff0c;正逐渐改变着我们的生活方式和消费体验。通过结合人工智能技术&#xff0c;这些无人自助咖啡机正在实现真正的“AI智能”制饮&#xff0c;为消费者带来全新的咖啡体验。…

Qt 项目使用visual studio 进行开发调试

https://marketplace.visualstudio.com/items?itemNameTheQtCompany.QtVisualStudioTools2015 https://devblogs.microsoft.com/cppblog/bring-your-existing-qt-projects-to-visual-studio/ 正常Qt开发中&#xff0c;使用Qt Creator 进行windows下MSVC编译器的调试是一件挺麻…

Medium 级别反射型 XSS 攻击演示(附链接)

环境准备 如何搭建 DVWA 靶场保姆级教程&#xff08;附链接&#xff09;https://eclecticism.blog.csdn.net/article/details/135834194?spm1001.2014.3001.5502 测试 打开靶场找到该漏洞页面 先右键检查输入框属性 跟 Low 级别是一样的&#xff0c;所以咱们直接输入带 HTM…

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

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

AI修复老照片的一些参数设置

很久没更新CSDN文章了&#xff0c;这次给粉丝带来老照片修复流程 1>用ps修图 图章工具 笔刷 画笔修复 2>高清放大 3>lineattile 重绘 4>上色 具体可参考我的B站视频。 下面是一些笔记。 best quality,masterpiece,photorealistic,8k,ultra high res,solo,ext…

概念解析 | 现象揭秘:经验模态分解的奥秘

注1:本文系"概念解析"系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:经验模态分解(Empirical Mode Decomposition, EMD) 概念解析 | 现象揭秘:经验模态分解的奥秘 Decomposing Signal Using Empirical Mode Decomposition — Algorith…

Spring MVC入门(4)

请求 获取Cookie/Session 获取Cookie 传统方式: RequestMapping("/m11")public String method11(HttpServletRequest request, HttpServletResponse response) {//获取所有Cookie信息Cookie[] cookies request.getCookies();//打印Cookie信息StringBuilder build…

Spring+thymeleaf完成用户管理页面的增删查改功能

目录 知识点&#xff1a; 路由重定向 redirect:/*** 登录 控制层代码 接口 sql配置 页面效果 添加用户 控制层代码 接口 sql配置 页面效果 查看信息 控制层代码 接口 sql配置 页面效果 修改信息 控制层代码 接口 sql配置 页面效果 条件查询 控制层代码 …

Vue3:用重定向方式,解决No match found for location with path “/“问题

一、情景说明 在初学Vue3的项目中&#xff0c;我们配置了路由后&#xff0c;页面会告警 如下图&#xff1a; 具体含义就是&#xff0c;没有配置"/"路径对应的路由组件 二、解决 关键配置&#xff1a;redirect const router createRouter({history:createWebHis…

「渗透笔记」致远OA A8 status.jsp 信息泄露POC批量验证

前言部分 在本节中&#xff0c;我会分两部分来说明致远OA A8 status.jsp 信息泄露的验证问题&#xff0c;其实就是两种验证方式吧&#xff0c;都一样&#xff0c;都是批量验证&#xff0c;主要如下所示&#xff1a; 通过Python脚本进行批量验证&#xff0c;但是前提是你可以收…

vue3+threejs新手从零开发卡牌游戏(九):添加抽卡逻辑和动效

首先优化下之前的代码&#xff0c;把game/deck/p1.vue中修改卡组方法和渲染卡组文字方法提到公共方法中&#xff0c;此时utils/common.ts完整代码如下&#xff1a; import { nextTick } from vue; import * as THREE from three; import * as TWEEN from tweenjs/tween.js impo…

哈希表(c++)

1、介绍 哈希表&#xff0c;也称为散列表&#xff0c;是一种非常高效的数据结构。它通过将键&#xff08;Key&#xff09;映射到数组的特定位置来快速查找、插入和删除数据。这个映射过程由哈希函数&#xff08;Hash Function&#xff09;完成&#xff0c;该函数将键转化为一个…

【JS】深度学习JavaScript

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【JS】深度学习JavaScript &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 一:JavaScript1.1 JavaScript是什么1.2 JS的引入方式1.3 JS变量1.4 数据类型1.5 …