JavaEE初阶-线程3

文章目录

  • 一、线程安全问题-内存可见性
  • 二、等待通知
    • 2.1 wait()方法
    • 2.2 notify()方法


一、线程安全问题-内存可见性

import java.util.Scanner;public class Demo27 {private static int count=0;//下面这段代码会出现内存的可见性问题//将从内存中读取count值的操作称为load 判断操作称为cmp//load和cmp的执行速度差了好几个数量级,在线程2开始执行代码提示输入数字时,线程1的while循环已经执行了很多遍//java编译器会自动给代码进行优化//导致load只是第一次时真正从内存中读取count值,其余都是从cpu的寄存器中读取//然而线程2修改count是在内存中进行修改,线程1根本访问不到count的值//可以在变量前加上volatile关键字来提醒编译器不要优化public static void main(String[] args) {Thread t1=new Thread(()->{while(count==0) {//}System.out.println("t1 执行结束");});Thread t2=new Thread(()->{Scanner scanner=new Scanner(System.in);System.out.println("输入数字:");count=scanner.nextInt();});t1.start();t2.start();}}

以上代码建立一个线程t1和线程t2,线程t1中建立一个循环,线程t2控制循环中的条件,按照预期,应该在t2中改变条件之后t1结束,整个程序也应该结束,但事实并非如此。
在这里插入图片描述
输入数字之后线程一仍然没有结束,这是因为count==0这个条件涉及到两个操作load和cmp,先将count的值从内存中载入cpu的寄存器进行比较,在线程二中还未输入数字时,线程一的循环已经执行很多次了,编译器发现这么多次count的值没有变化就会进行优化,只有第一个load会从内存中读count的值,剩余次数都是直接用寄存器中的值,这样既使后来在线程二当中改变了内存中的count的值,因为循环条件的count不从内存中读值因此访问不到修改后的count,线程一就会继续循环,从而程序就结束不了。如果不想让编译器进行优化,可以在count前面加上volatile关键字即可。
另外上述的内存可见性问题加锁,即包含在synchronized内也可以解决,因为加锁是比较重量的,load相对就不算慢了就不会触发优化。在循环内加上sout输出语句也是一个道理,sout相对于load更慢无法触发优化,自然也能解决上述的内存可见性的问题。

在网上查询资料还可以看到以一种JMM(java内存模型)角度来理解该问题的解答:
当t1执行的时候,会从工作内存中读取count而不是从主内存中。
t2修改count会先修改工作内存,再同步到主内存,但由于t1没有重新读主内存,导致t1没有感知到t2的修改。

二、等待通知

2.1 wait()方法

代码示例如下:

public class Demo28 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();System.out.println("等待之前");//wait必须在synchronized内使用,会解锁并且让线程进入阻塞等待状态synchronized (locker) {locker.wait();}System.out.println("等待之后");}
}

wait方法是object类的方法,任何类对象都可以使用,它只能在锁内使用,如果执行wait方法会释放锁并且线程进入阻塞状态waiting。wait也有限时的参数,加上时间,会在限定时间内阻塞之后自动唤醒。
注意:wait和sleep一样可以被interrupt打断并且清空标志位。

2.2 notify()方法

通过notify方法来唤醒wait方法进入阻塞的线程,wait的线程状态从Waiting->Runnable->Blocked。
代码示例如下:

public class Demo29 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();//1.wait和notify必须都在synchronized内//2.一个notify唤醒一个wait() 如果多个线程wait就使用多个notify或者notifyAll 这种唤醒时随机的//3.如果想唤醒指定线程也可以必须一对一写好锁对象//4.notify需要在wait之后Thread t1 = new Thread(() -> {System.out.println("t1 等待之前");synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1 等待之后");});Thread t2 = new Thread(() -> {System.out.println("t2 通知之前");Scanner scanner = new Scanner(System.in);synchronized (locker) {
//控制阻塞,不输入数字t1仍是阻塞scanner.nextInt();locker.notify();}System.out.println("t2 通知之后");});t1.start();t2.start();}}

notify必须也在synchronized之内,并且一个notify只能唤醒对应的一个wait,如果多个线程wait就使用多个notify或者notifyAll,这种唤醒时随机的,唤醒的前提是通过一个对象来调用的wait及notify方法。如果想唤醒指定线程也可以必须一对一写好锁对象。另外notify的使用必须在wait之后,如果在wait之前使用notify不会有任何效果,不过wait就没有办法唤醒了。
使用相应的锁对象进行一对一唤醒代码示例如下:

public class Demo30 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();Object locker1 = new Object();Thread t1 = new Thread(() -> {synchronized (locker) {System.out.println("t1等待之前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1等待之后");}});Thread t2 = new Thread(() -> {synchronized (locker1) {System.out.println("t2等待之前");try {locker1.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2等待之后");}});Thread t3 = new Thread(() -> {synchronized (locker) {//locker.notifyAll();locker.notify();}synchronized (locker1) {locker1.notify();}});t1.start();t2.start();Thread.sleep(1000);t3.start();}}

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

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

相关文章

为什么企业需要ERP帮助中心?看完你就懂了

企业资源计划(ERP)系统,这个听起来十分高大上的名词,实际上是许多现代企业后台运行的大脑。从财务管理、人力资源到生产操作和供应链管理,ERP系统如同一个企业的神经中枢,协调着各个部门之间的信息流。但是…

useBlocker 防止页面跳转导致的表单丢失

关于useBlocker The hook allows you to prevent the user from navigating away from the current location, and present them with a custom UI to allow them to confirm the navigation 在react-router的v6版本之前&#xff0c;我们会使用<Prompt />组件来拦截路由的…

【JAVASE】学习面向对象程序三大特性之一( 封装)

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609;\n &#x1f34e;个人主页&#xff1a;再无B&#xff5e;U&#xff5e;G-CSDN博客 目标&#xff1a; 1.包的使用 2.static关键字的使用 3.代码…

随机生成Long全范围数

随机生成Long全范围数 前言实现思路主要代码分区随机生成过程案例&#xff1a;随机生成100个数 朴素的比较总结 前言 使用自带的Random.nextLong()函数生成Long型的长整数&#xff0c;范围比较小&#xff0c;如下图。100个随机数没看见10以内的数字。所以考虑实现随机化生成大…

golang语言系列:Scrum、Kanban等敏捷管理策略

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 golang语言系列 文章&#xff0c;主要对编程通用技能 Scrum、Kanban等敏捷管理策略 进行学习 1.什么是敏捷开发 敏捷是一个描述软件开发方法的术语&#xff0c;它强调增量交付、团队协作、持续规划和持续学习。…

代码随想录算法训练营第二十九天(回溯5)|491. 非递减子序列、46. 全排列、47. 全排列 II(JAVA)

文章目录 491. 非递减子序列解题思路源码 46. 全排列解题思路源码 47. 全排列 II解题思路源码 总结 491. 非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 …

【ai-动态维基百科】

初体验 今日份分享&#xff0c; 有趣工具&#xff0c;ai-动态维基百科&#xff0c;Globe Explorer。 介绍&#xff1a;基于人工智能(gpt3.5, gpt4.0)的搜索引擎&#xff0c;帮助用户以视觉化的方式探索各种主题。它使用大型语言模型来理解查询&#xff0c;并生成详细的主题页面…

【Python】快速排序法 Leetcode 148. 排序链表

题目 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 代码 第一次使用朴素快速排序&#xff0c;基准值为头节点值&#xff0c;居然超时了&#xff0c;然后…

大模型学习笔记一

前言 随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习&#xff0c;本文就介绍了机器学习的基础内容。 一、AI是什么&#xff1f; 二、大模型能干什么 大模型&#xff0c;全称「大语言模型」&#xff0c;英文「Large…

基于STC12C5A60S2系列1T 8051单片机的数码管显示IIC总线器件AT24C02记录单片机上电次数应用

基于STC12C5A60S2系列1T 8051单片机的数码管显示IIC总线器件AT24C02记录单片机上电次数应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器LCD1602简单介绍…

宏景eHR showmediainfo SQL注入漏洞复现

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合,满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR showmediainfo接口处存在SQL注入漏洞,未经过身份认证的远程攻击者可利用此漏洞执行任意SQL指令,从而窃取数据库敏感…

python--不死兔子问题

def rabbit(n):if n < 3:return 1return rabbit(n - 1) rabbit(n - 3)if __name__ __main__:print(rabbit(4))