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

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

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

相关文章

深度学习算法简介(一)

目录 ⛳️推荐 前言 1、深度神经网络&#xff08;DNN&#xff09; 2、卷积神经网络&#xff08;CNN&#xff09; 3、残差网络&#xff08;ResNet&#xff09; 4、LSTM&#xff08;长短时记忆网络&#xff09; 5、Word2Vec 6、Transformer 7、生成对抗网络&#xff08;…

kmeans实现图像像素分类

代码 import tkinter as tkfrom tkinter import filedialogfrom PIL import Image, ImageTkimport numpy as np import random import mathclass Cluster(object):def __init__(self):# pixels是像素的意思&#xff0c;这里定义一个像素组用来存放像素的值self.pixels []# 创…

HubSpot功能有哪些?

HubSpot是一个功能丰富的平台&#xff0c;主要涵盖市场营销、销售、客户服务和客户关系管理&#xff08;CRM&#xff09;等领域。以下是HubSpot的一些主要功能&#xff1a; 市场营销自动化&#xff1a;HubSpot允许用户制定和执行多渠道的市场营销活动&#xff0c;包括创建和管…

力扣HOT100 - 105. 从前序与中序遍历序列构造二叉树

解题思路&#xff1a; 分治 以中序遍历为参照&#xff0c;用前序遍历的节点构建二叉树。 root 1 index - left表示前序遍历右子树的开始节点&#xff0c;即当前节点的下一个节点左子树长度。 class Solution {int[] preorder;HashMap<Integer, Integer> map new Ha…

C#基础:WPF中常见控件的布局基础

一、用ViewBox实现放缩控件不变 二、布局代码 <Window x:Class"WpfApp1.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"Title"MainWindow"…

小瓶清洗桶抗酸碱耐高温PFA清洗器半导体实验清洗用

PFA清洗桶&#xff0c;也叫PFA清洗器、PFA小瓶清洗桶&#xff0c;主要用于清洗浸泡实验室小型PFA溶样罐和烧杯等&#xff0c;带有密封螺纹盖&#xff0c;可以用于摇晃&#xff0c;高纯耐高温材质可放置电热板上加热使用。 特点&#xff1a;可拆卸倒酸口&#xff0c;可安全倒出酸…

ctfshow菜狗杯 web 无算力以及easyPytHon_P

web签到题 error_reporting(0); highlight_file(__FILE__);eval($_REQUEST[$_GET[$_POST[$_COOKIE[CTFshow-QQ群:]]]][6][0][7][5][8][0][9][4][4]);套娃传参 中文要编码 Cookies &#xff1a;CTFshow-QQ%E7%BE%A4:a POST:ab GET:?bc&c[6][0][7][5][8][0][9][4][4]syste…

干货收藏:CRM系统帮助中心设计教程

CRM系统&#xff0c;也就是客户关系管理系统&#xff0c;是企业运营中的得力助手&#xff0c;但太复杂的CRM系统有时候用起来也挺让人头疼的。所以&#xff0c;一个好用、易懂的帮助中心就显得尤为重要啦&#xff01;今天我来跟大家分享一下关于CRM系统帮助中心的设计教程。 1.…

09 MySQL--操作真题

1. not in 用一条 SQL 语句&#xff0c;查询出每门课程都大于 80 分的人。 分析&#xff1a; 去重查询出存在课程小于 80 分的人&#xff0c;设为集合A查询不在集合 A 中的人 # 第一步&#xff1a;找小于等于80分的学员姓名 select distinct name from t_student where fens…

Transformer - 特征预处理

Transformer - 特征预处理 flyfish 原始数据 train_data.values [[ 5.827 2.009 1.599 0.462 4.203 1.34 30.531][ 5.76 2.076 1.492 0.426 4.264 1.401 30.46 ][ 5.76 1.942 1.492 0.391 4.234 1.31 30.038][ 5.76 1.942 1.492 0.426 4.234 1.31…

Linux命令超详细介绍

目录 安装 Linux的目录结构&#xff1a; Linux命令入门&#xff1a; Linux命令的基础格式&#xff1a; 例子&#xff1a; ls 参数 选项 注意&#xff1a; 目录切换命令&#xff1a;cd/pwd cd: pwd: 相对路径和绝对路径&#xff1a; mkdir 不用参数&#xff1a; …

【Node.js工程师养成计划】之打造自己的脚手架工具

一、创建全局的自定义命令 1、打开一个空文件夹&#xff0c;新建一个bin文件夹&#xff0c;在bin文件夹下新建cli.js文件&#xff0c;js文件可以命名为cli.js&#xff08;您随意&#xff09; 2、在cli.js文件中的开头&#xff08;&#xff01;&#xff01;&#xff09;写下面这…

NeRF in the Wild: Neural Radiance Fields for Unconstrained Photo Collections

NeRF in the Wild: Neural Radiance Fields for Unconstrained Photo Collections(野外的 NERF: 用于无约束照片采集的神经辐射场&#xff09; Abstract 我们提出了一种基于学习的方法来合成新的视图的复杂场景使用只有非结构化的收集野生照片。我们建立在神经辐射场(neRF)的…

对于地理空间数据,PostGIS扩展如何在PostgreSQL中存储和查询地理信息?

文章目录 一、PostGIS扩展简介二、PostGIS存储地理空间数据1. 创建空间数据表2. 插入空间数据 三、PostGIS查询地理空间数据1. 查询指定范围内的地理空间数据2. 计算地理空间数据之间的距离3. 对地理空间数据进行缓冲区分析 四、总结 地理空间数据是指描述地球表面物体位置、形…

11、【桥接模式】让将抽象和实现分离,使得它们可以独立地变化

你好&#xff0c;我是程序员雪球。 今天我们来聊聊 23 种设计模式中&#xff0c;一种常见的结构型模式&#xff0c;桥接模式。聊聊它的设计思想、应用场景&#xff0c;以及如何使用。 一、设计思想 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#…

Python slice切片

1. 切片简介 取一个str、list、tuple的部分元素是非常常见的操作 切片 译自英文单词slice,指的是一部分切片 根据 步长step 从原序列中取出一部分元素组成新序列切片适用于 字符串、列表、元组 2. 切片的格式 字符串[开始索引:结束索引:步长] 包含开始索引, 不包含结束索…

vue+springboot实验个人信息,修改密码,忘记密码功能实现

前端部分 新增Person&#xff08;个人页面&#xff09;&#xff0c;Password&#xff08;修改密码页面&#xff09;&#xff0c;还需要对Manager&#xff0c;login页面进行修改 router文件夹下的index.js&#xff1a; import Vue from vue import VueRouter from vue-router i…

在 vue3 中使用高德地图

前言&#xff1a;定位地图位置所需要的经纬度&#xff0c;可以通过 拾取坐标 获取。 一&#xff1a;快速上手 1. 安装依赖 npm install amap/amap-jsapi-loader # or pnpm add amap/amap-jsapi-loader # or yarn add amap/amap-jsapi-loader 2. 创建组件 src/components/Ma…

飞书小技巧:markdown导出

文章目录 下载Feishu2Md飞书应用配置配置feishu2md工具绑定应用导出markdown 下载Feishu2Md Feishu2Md 飞书应用配置 进入飞书开发者后台 https://open.feishu.cn/app。 点击“创建企业自建应用”&#xff0c;并填写应用名称等信息。而后点击创建。 PS: 此处作者创建应用名…

C++ | Leetcode C++题解之第42题接雨水

题目&#xff1a; 题解&#xff1a; class Solution { public:int trap(vector<int>& height) {int n height.size();if (n 0) {return 0;}vector<int> leftMax(n);leftMax[0] height[0];for (int i 1; i < n; i) {leftMax[i] max(leftMax[i - 1], he…