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

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();}}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • CentOS7:Python版本回退
  • Linux下Qt生成程序崩溃文件
  • 24双非考研哈尔滨工程大学计算机(@程程笔记)
  • hydra九头蛇
  • 海纳斯删除广告位
  • 【环境变量】基本概念理解 | 查看环境变量echo | PATH的应用和修改
  • 每日OJ题_两个数组dp①_力扣1143. 最长公共子序列
  • SpringBoot启动禁用员工账号(动态sql通用修改)
  • “大学论文数据分析全攻略:从理论到实践的实用指南“
  • Redis中的Sentinel(五)
  • FPGA的就业前景
  • 基于Java+SpringBoot+Mybaties+layui+Vue+elememt 实习管理系统 的设计与实现
  • 1.0-spring入门
  • Transformer Based Multi-view Network for Mammographic Image Classification
  • 加密软件VMProtect教程:使用脚本-功能
  • 时间复杂度分析经典问题——最大子序列和
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • [NodeJS] 关于Buffer
  • [原]深入对比数据科学工具箱:Python和R 非结构化数据的结构化
  • C语言笔记(第一章:C语言编程)
  • Java方法详解
  • Python学习笔记 字符串拼接
  • React as a UI Runtime(五、列表)
  • vue-cli在webpack的配置文件探究
  • 大数据与云计算学习:数据分析(二)
  • 动态规划入门(以爬楼梯为例)
  • 简单易用的leetcode开发测试工具(npm)
  • 栈实现走出迷宫(C++)
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • # Kafka_深入探秘者(2):kafka 生产者
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • # 达梦数据库知识点
  • #stm32整理(一)flash读写
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (二)fiber的基本认识
  • (二)pulsar安装在独立的docker中,python测试
  • (二)丶RabbitMQ的六大核心
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (回溯) LeetCode 131. 分割回文串
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (游戏设计草稿) 《外卖员模拟器》 (3D 科幻 角色扮演 开放世界 AI VR)
  • (转)memcache、redis缓存
  • .CSS-hover 的解释
  • .FileZilla的使用和主动模式被动模式介绍
  • .net wcf memory gates checking failed
  • .考试倒计时43天!来提分啦!
  • /usr/bin/env: node: No such file or directory