[Java EE] 多线程(四):线程安全问题(下)

1.5 volatile关键字

我们在了解这个关键字之前,我们首先要把产生线程安全的第4个原因补齐,我们来说说由于内存可见性引起的线程安全问题.
我们来看下面这样一段代码:

import java.util.Scanner;public class Demo16 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {while (count == 0) {;}System.out.println("count的值被修改");});Thread thread1 = new Thread(()->{Scanner scanner = new Scanner(System.in);count = scanner.nextInt();});thread.start();Thread.sleep(1000);thread1.start();}
}

由上述代码的逻辑,我们预期的结果是:在控制台的地方输入了一个不为0的数字的时候,上面的循环条件不满足,跳出循环,打印"count的值被修改",但是我们运行起来发现:
在这里插入图片描述
输入了不为0的值之后,毛都没有!!
为什么会出现这种情况呢?我们首先要了解一下Java中的内存模型(JMM),即Java虚拟机规范中定义的Java内存模型.
在这里插入图片描述
设计这种内存模型的目的就是为了保证:屏蔽掉各种硬件和操作系统的访问差异,避免由于操作系统不同和硬件结构的不同而引起的不兼容,真正做到"一次编译,到处运行".

  • 线程之间的共享变量存在主内存(Main Memory).(硬件角度总真正的内存空间)
  • 每⼀个线程都有自己的"⼯作内存"(Working Memory).(硬件角度的CPU中的寄存器)
  • 线程想要读取一个共享变量的时候,需要先从主内存中拷贝到工作内存中.
  • 线程想要修改一个共享变量的时候,先修改工作内存中的副本,再同步回主内存中.

但是当计算机发现每次从主内存中拷贝数据到工作内存中时,每次拷贝的数据都是一样的,而且工作内存的访问要比主内存的访问快好几个数量级,这就使得计算机不得不做出优化操作,每次读取变量的时候,把拷贝这一步优化掉了,直接读取的是工作内存中的数据,这就使得在另一个线程在主内存中修改这个共享变量的时候,另一个线程读取不到,也就无法改变工作内存中的数据.
但是我们在循环中加上一个print操作的时候,我们会惊奇地发现,循环可以停下来了:

import java.util.Scanner;
public class Demo17 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {while (count == 0) {System.out.println("tread");//IO输出比count判断慢得多}System.out.println("count的值被修改");});Thread thread1 = new Thread(()->{Scanner scanner = new Scanner(System.in);count = scanner.nextInt();});thread.start();Thread.sleep(1000);thread1.start();}
}

在这里插入图片描述
这又是为什么呢?是因为由于拷贝操作时内存方面的操作,速度肯定又要比IO操作快好几个数量级.而IO操作每次执行的结果注定是不同的,这就使得JVM不可以优化掉IO操作,即然IO操作都优化不到,比他快好几个数量级的拷贝操作肯定优化不到.

举例说明:年会不能停
有请助教:潘妮,马杰克,胡建林,杰弗瑞.
假如现在没有胡建林,现在潘妮就相当于主内存与工作内存中的拷贝操作,马杰克就相当于直接从工作内存中读取的操作.如今杰弗瑞要裁员,由于马杰克的工作效率和工作能力比潘妮大上好几个数量级,裁员的时候优先裁的就是潘妮,但是现在歪打正着混进个胡建林,就相当于IO操作,由于胡建林是靠着"关系"进入的众和集团,即使胡建林干活再慢,也不可以裁掉,即然比潘妮干活慢的胡建林都裁不掉,何谈裁潘妮.
在这里插入图片描述

现在如果没有IO操作,还想要while循环停下来,我们就可以引入volatile关键字来对变量进行修饰.告诉JVM,该变量必须每次都到主内存中读取,不可以进行优化操作,就相当于告诉老板,裁员的时候请不要裁到大动脉!!!

import java.util.Scanner;public class Demo18 {public static volatile int count = 0;//使用volatile告诉编译器该变量不可优化public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {while (count == 0) {;}System.out.println("count的值被修改");});Thread thread1 = new Thread(()->{Scanner scanner = new Scanner(System.in);count = scanner.nextInt();});thread.start();Thread.sleep(1000);thread1.start();}
}

在这里插入图片描述
但是我们需要注意的是:volatile不保证原子性

public class Demo21 {public static volatile int count = 0;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread thread1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});thread.start();thread1.start();thread.join();thread1.join();System.out.println(count);}
}

在这里插入图片描述
我们看到,即使给count变量加了volatile关键字,还是会存在线程安全问题.

1.6 wait与notify–>线程的等待通知机制

由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.

举例说明:光与影
有请助教:黑子哲也,青峰大辉,火神大我
在与漂亮国的对决中,他们像拿到一个二分球,就先需要大辉先抢球,之后传球给黑子,黑子通过回旋一击,使得篮球改变运动轨迹,之后通过大我的扣篮完成完美一击.
他们中间的先后顺序,但凡慢了一步,或者是快了一步,都会丢失这2分.
在这里插入图片描述
在这里插入图片描述

完成这里的协调工作,需要涉及到三个方法:

  • wait()/wait(time):让线程进入等待状态
  • notify()/notifyAll():唤醒在当前对象上等待的所有线程
    注意:这几个方法都是Object类的方法

1.6.1 wait()方法

wait做的事情:

  • 释放当前对象的锁
  • 使得当前线程进入阻塞等待状态
  • 满足一定条件被唤醒之后,尝试重新获取锁
    注意:wait要搭配synchronized使用,否者在wait()方法解锁的时候,找不到对应的锁,就会抛出异常.
    wait结束等待的条件:
  • 其他线程使用调用notify方法唤醒该线程.
  • wait时间超时.
  • 其他线程调用该线程的interrupted方法,导致线程被强制唤醒,抛出异常.

1.6.2 notify()方法

notify⽅法是唤醒等待的线程.
• ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
• 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈wait状态的线程。(并没有"先来后到")
• 在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏
,也就是退出同步代码块之后才会释放对象锁。
使用示例:

public class Demo19 {public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread thread = new Thread(()->{synchronized (object){System.out.println("等待开始");try {object.wait();//1.解锁,此时tread1才可以加锁 2.阻塞等待 3.唤醒之后再尝试获取锁 4.唤醒之后tread1未解除锁,阻塞} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("等待结束");//所以等待结束的打印一定在唤醒结束之后}});Thread thread1 = new Thread(()->{synchronized (object){System.out.println("唤醒开始");object.notify();System.out.println("唤醒结束");}});thread.start();Thread.sleep(1000);thread1.start();}
}

注意:在tread启动之后,必须sleep1s,由于线程调度的随机性,不加的话很可能先调度tread1现成,这样notify就会被wait错过

1.6.3 notifyAll()方法

上述notify方法只能随机唤醒一个线程:

public class Demo20 {public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread thread = new Thread(()->{synchronized (object){System.out.println("等待开始");try {object.wait();//1.解锁,此时tread1才可以加锁 2.阻塞等待 3.唤醒之后再尝试获取锁 4.唤醒之后tread1未解除锁,阻塞} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("等待结束");//所以等待结束的打印一定在唤醒结束之后}});Thread thread2 = new Thread(()->{synchronized (object){System.out.println("等待开始1");try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("等待结束1");}});Thread thread1 = new Thread(()->{synchronized (object){System.out.println("唤醒开始");object.notify();System.out.println("唤醒结束");}});thread.start();thread2.start();Thread.sleep(1000);thread1.start();}
}

运行结果:只唤醒了tread线程
在这里插入图片描述
如果想要一次性唤醒所有线程,就要用到notifyAll()方法:

public class Demo20 {public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread thread = new Thread(()->{synchronized (object){System.out.println("等待开始");try {object.wait();//1.解锁,此时tread1才可以加锁 2.阻塞等待 3.唤醒之后再尝试获取锁 4.唤醒之后tread1未解除锁,阻塞} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("等待结束");//所以等待结束的打印一定在唤醒结束之后}});Thread thread2 = new Thread(()->{synchronized (object){System.out.println("等待开始1");try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("等待结束1");}});Thread thread1 = new Thread(()->{synchronized (object){System.out.println("唤醒开始");object.notifyAll();System.out.println("唤醒结束");}});thread.start();thread2.start();Thread.sleep(1000);thread1.start();}
}

运行结果:线程全部被唤醒
在这里插入图片描述
在两个线程全部被唤醒之后,由于唤醒之后线程会尝试重新获取锁,tread和tread2还是会发生锁竞争.

举例:面试
有请助教:滑稽老铁
notify()方法:
在这里插入图片描述
notifyAll()方法:
在这里插入图片描述

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

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

相关文章

HTML随机点名程序

案例要求 1.点击点名按钮&#xff0c;名字界面随机显示&#xff0c;按钮文字由点名变为停止 2.再次点击点名按钮&#xff0c;显示当前被点名学生姓名&#xff0c;按钮文字由停止变为点名 案例源码 <!DOCTYPE html> <html lang"en"> <head> <m…

动态规划——斐波那契数列模型:746.使用最小花费爬楼梯

文章目录 题目描述算法原理解法一1.状态表示2.状态转移方程3.初始化4.填表顺序5.返回值 解法二1.状态表示2.状态转移方程3.初始化4.填表顺序5.返回值 代码实现解法一&#xff1a;C解法一&#xff1a;Java解法二&#xff1a;C解法二&#xff1a;Java 题目描述 题目链接&#xf…

K8S探针分享

一&#xff0c;探针介绍 1 探针类型 livenessProbe&#xff1a;存活探针&#xff0c;用于判断容器是不是健康&#xff1b;如果探测失败&#xff0c;Kubernetes就会重启容器。 readinessProbe&#xff1a;就绪探针&#xff0c;用于判断是否可以将容器加入到Service负载均衡池…

STM32H7使用FileX库BUG,SD卡挂载失败

问题描述&#xff1a; 使用STM32H7ThreadXFileX&#xff0c;之前使用swissbit牌的存储卡可正常使用&#xff0c;最近项目用了金士顿的存储卡&#xff0c;发现无法挂载文件系统。 原因分析&#xff1a; 调试过程发现&#xff0c;关闭D-Cache可以挂载使用exfat文件系统。 File…

接口测试全流程扫盲

扫盲内容&#xff1a; 1.什么是接口&#xff1f; 2.接口都有哪些类型&#xff1f; 3.接口的本质是什么&#xff1f; 4.什么是接口测试&#xff1f; 5.问什么要做接口测试&#xff1f; 6.怎样做接口测试&#xff1f; 7.接口测测试点是什么&#xff1f; 8.接口测试都要掌…

pytest-xdist:远程多主机 - 分布式运行自动化测试

简介&#xff1a;pytest-xdist插件使用新的测试执行模式扩展了pytest&#xff0c;最常用的是在多个CPU之间分发测试以加快测试执行&#xff0c;即 pytest -n auto同时也是一个非常优秀的分布式测试插件&#xff0c;分别支持ssh和socket两种方式实现master和worker的远程通讯。…

Linux 第十一章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

Ubuntu终端常用指令

cat cat 读取文件的内容 1、ls 一、 1、ll 显示当前目录下文件的详细信息,包括读写权限,文件大小,文件生成日期等(若想按照更改的时间先后排序,则需加-t参数,按时间降序(最新修改的时间排在最前)执行: $ ll -t, 按时间升序执行: $ ll -t | tac): ll 2、查看当前所处路径(完整…

自然语言处理: 第二十八章大模型基底之llama3

项目地址: meta-llama/llama3: The official Meta Llama 3 GitHub site 前言 LLaMa系列一直是人们关注的焦点&#xff0c;Meta在4月18日发布了其最新大型语言模型 LLaMA 3。该模型将被集成到其虚拟助手Meta AI中。Meta自称8B和70B的LLaMA 3是当今 8B 和 70B 参数规模的最佳模…

【oj题解】二分算法、二分答案

1909 - 跳石头 题目描述 一年一度的“跳石头”比赛又要开始了! 这项比赛将在一条笔直的河道中进行&#xff0c;河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间&#xff0c;有 N 块岩石&#xff08;不含起点和终点的岩石&#xf…

Qt:学习笔记一

一、工程文件介绍 1.1 main.cpp #include "widget.h" #include <QApplication> // 包含一个应用程序类的头文件 //argc&#xff1a;命令行变量的数量&#xff1b;argv&#xff1a;命令行变量的数组 int main(int argc, char *argv[]) {//a应用程序对象&…

朴素贝叶斯算法分类

def loadDataSet():postingList[[my, dog, has, flea, problems, help, please], #切分的词条[maybe, not, take, him, to, dog, park, stupid],[my, dalmation, is, so, cute, I, love, him],[stop, posting, stupid, worthless, garbage],[mr, licks, ate, my, steak, …

Linux - tar (tape archive)

tar 的全称是 Tape Archive。它最初是在 Unix 系统中用于将数据写入磁带的工具&#xff0c;但现在它通常用于创建、维护、修改和提取文件的归档文件。尽管 tar 可以用于压缩和解压缩文件&#xff0c;但它本身并不进行压缩&#xff0c;而是通常与 gzip 或 bzip2 等压缩工具一起使…

记录——FPGA的学习路线

文章目录 一、前言二、编程语言2.1 书籍2.2 刷题网站2.3 仿真工具 三、基础知识3.1 专业基础课3.2 fpga相关专业知识 四、开发工具五、动手实验 一、前言 也不是心血来潮想学习fpga了&#xff0c;而是祥哥还有我一个国科大的同学都在往fpga这个方向走 并且看过我之前文章的同…

springboot+springsecurity+vue前后端分离权限管理系统

有任何问题联系本人QQ: 1205326040 1.介绍 优秀的权限管理系统&#xff0c;核心功能已经实现&#xff0c;采用springbootvue前后端分离开发&#xff0c;springsecurity实现权限控制&#xff0c;实现按钮级的权限管理&#xff0c;非常适合作为基础框架进行项目开发。 2.效果图…

EureKa技术解析:科技行业的革新风暴(ai写作)

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

【Hadoop】-Apache Hive使用语法与概念原理[15]

一、数据库操作 创建数据库 create database if not exists myhive; 使用数据库 use myhive; 查看数据库详细信息 desc database myhive; 数据库本质上就是在HDFS之上的文件夹。 默认数据库的存放路径是HDFS的&#xff1a;/user/hive/warehouse内 创建数据库并指定hdfs…

盛水最多的容器 ---- 双指针

题目链接 题目: 分析: 最大容积 即使就是最大面积, 长为下标之差, 宽为两下标对应值的最小值解法一: 暴力枚举: 将每两个数之间的面积都求出来, 找最大值, 时间复杂度较高解法二: 假设我们的数组是[6, 2, 5, 4], 我们先假设最左边和最右边, 即6 和 4 之间是最大面积长a*宽b此…

Xcode隐私协议适配

1. Privacy manifest files 1.1 简介 自己App或三方SDK&#xff08;通过XCFrameworks|Swift packages|Xcode projects集成的&#xff09;需要包含一个隐私清单文件&#xff08;privacy manifest&#xff09;叫作 PrivacyInfo.xcprivacy。它是一个属性列表&#xff0c;记录了A…

CSS之显示覆盖内容(z-index)

前言&#xff1a; 我们有的时候&#xff0c;希望下方的内容能够显示到上方&#xff0c;达到类似于多个图层的效果&#xff0c;此时我们可以利用z-index这个属性。 介绍&#xff1b; z-index属性值是用来设置元素的堆叠顺序(元素层级)。 覆盖原则&#xff1a; <1>特殊…