Java之线程篇四

目录

volatile关键字

volatile保证内存可见性

代码示例

代码示例2-(+volatile)

volatile不保证原子性

synchronized保证内存可见性

wait()和notify()

wait()方法

notify()

理解notify()和notifyAll()

wait和sleep的对比


volatile关键字
volatile保证内存可见性

volatile 修饰的变量, 能够保证 "内存可见性".

代码在写入 volatile 修饰的变量的时候:

改变线程工作内存中volatile变量副本的值
将改变后的副本的值从工作内存刷新到主内存

代码在读取 volatile 修饰的变量的时候: 

从主内存中读取volatile变量的最新值到线程的工作内存中
从工作内存中读取volatile变量的副本 

加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了。 

代码示例
public class Demo13 {private static int isQuit=0;public static void main(String[] args) {Thread t1=new Thread(()->{while(isQuit==0){}System.out.println("t1 退出");});t1.start();Thread t2=new Thread(()->{System.out.println("请输入 isQuit:");Scanner scanner=new Scanner(System.in);isQuit=scanner.nextInt();});t2.start();}
}

运行结果

通过jconsole观察,会看到线程t1处于RUNNABLE状态。

t1 读的是自己工作内存中的内容 .
t2 flag 变量进行修改 , 此时 t1 感知不到 flag 的变化 .

原因解释:

1) load 读取内存中isQuit的值到寄存器中.
2)通过cmp 指令比较寄存器的值是否是0.决定是否要继续循环.
由于这个循环,循环速度飞快.短时间内,就会进行大量的循环.也就是进行大量的load和cmp 操作.此时,编译器/JVM就发现了,虽然进行了这么多次load,但是 load 出来的结果都一样的.并且, load 操作又非常费时间,一次load花的时间相当于上万次cmp 了.
所以编译器就做了一个大胆的决定~~只是第一次循环的时候才读了内存.后续都不再读内存了,而是直接从寄存器中,取出isQuit的值了. 

代码示例2-(+volatile)
public class Demo13 {private static volatile int isQuit=0;public static void main(String[] args) {Thread t1=new Thread(()->{while(isQuit==0){}System.out.println("t1 退出");});t1.start();Thread t2=new Thread(()->{System.out.println("请输入 isQuit:");Scanner scanner=new Scanner(System.in);isQuit=scanner.nextInt();});t2.start();}
}

运行结果

代码示例3-(+sleep)

public class Demo13 {private static int isQuit=0;public static void main(String[] args) {Thread t1=new Thread(()->{while(isQuit==0){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1 退出");});t1.start();Thread t2=new Thread(()->{System.out.println("请输入 isQuit:");Scanner scanner=new Scanner(System.in);isQuit=scanner.nextInt();});t2.start();}
}

运行结果

volatile不保证原子性

代码示例

class Counter {volatile public int count = 0;void increase() {count++;}
}public class Demo13 {public static void main(String[] args) throws InterruptedException {final Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.count);}
}

运行结果

我们会发现,加上volatile以后,依旧不是线程安全的。

synchronized保证内存可见性

代码示例

class Counter {public int flag = 0;
}public class Demo13 {public static void main(String[] args) {Counter counter = new Counter();Thread t1 = new Thread(() -> {while (true) {synchronized (counter) {if (counter.flag != 0) {break;}}}System.out.println("循环结束!");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("输入一个整数:");counter.flag = scanner.nextInt();});t1.start();t2.start();}
}

运行结果

wait()和notify()
wait()方法

wait 做的事情:

使当前执行代码的线程进行等待. (把线程放到等待队列中)
释放当前的锁
满足一定条件时被唤醒, 重新尝试获取这个锁. 

wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

代码示例

public class Demo14 {public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println("wait 之前");// 把 wait 要放到 synchronized 里面来调用. 保证确实是拿到锁了的.object.wait();System.out.println("wait 之后");}}
}

 运行结果

此时object就会一直进行wait,当然我们肯定不想让程序一直等待下去,下面将介绍notify()来唤醒它。

notify()

notify 方法是唤醒等待的线程. 

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

代码示例

public class Demo15 {public static void main(String[] args) {Object object = new Object();Thread t1 = new Thread(() -> {synchronized (object) {System.out.println("wait 之前");try {object.wait();
//                    object.wait(5000);//也可以指定等待时间后自动唤醒} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("wait 之后");}});Thread t2 = new Thread(() -> {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (object) {System.out.println("进行通知");object.notify();}});t1.start();t2.start();}
}

运行结果

notifyAll()
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
代码示例
class WaitTask implements Runnable {private Object locker;public WaitTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {while (true) {try {System.out.println("wait 开始");locker.wait();System.out.println("wait 结束");} catch (InterruptedException e) {e.printStackTrace();}}}}
}
class NotifyTask implements Runnable {private Object locker;public NotifyTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {System.out.println("notify 开始");locker.notifyAll();System.out.println("notify 结束");}}
}public class Demo16 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(new WaitTask(locker));Thread t3 = new Thread(new WaitTask(locker));Thread t4 = new Thread(new WaitTask(locker));Thread t2 = new Thread(new NotifyTask(locker));t1.start();t2.start();t3.start();Thread.sleep(5000);t4.start();}
}

运行结果

注意: 虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁. 所以并不是同时执行, 而仍然是有先有后的执行.

理解notify()和notifyAll()
notify 只唤醒等待队列中的一个线程 . 其他线程还是乖乖等着.

notifyAll 一下全都唤醒, 需要这些线程重新竞争锁.

wait和sleep的对比
唯一的相同点就是都可以让线程放弃执行一段时间.
1. wait 需要搭配 synchronized 使用 . sleep 不需要 .
2. wait Object 的方法 sleep Thread 的静态方法 .

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于yolov8的茶叶病害检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的茶叶病害检测系统&#xff0c;是利用深度学习技术&#xff0c;特别是YOLOv8这一先进的目标检测算法&#xff0c;来精准识别和监测茶叶生长过程中出现的各种病害。该系统通过无人机、地面机器人或固定摄像头等设备&#xff0c;定期采集茶园的高分辨率…

MATLAB窗口操作常用命令

MATLAB窗口操作常用命令 命令功能clc清除窗口命令clear commandclf清除图形对象(窗口)clear清除工作区所有变量 释放内存clear all清除工作区的所有变量和函数type显示指定文件的所有内容与CMD命令类似dir查看当前工作文件夹中的内容与CMD命令类似save保存工作区或工作区中任何…

多维度智能体验:引领未来的RAG型知识图谱数字

随着人工智能技术的发展&#xff0c;传统的数字人已逐渐普及&#xff0c;但大多数仍然局限于简单的文本回复或预设答案&#xff0c;缺乏深入的语义理解和个性化互动能力&#xff0c;难以应对复杂、多维度的问题交互。不同于传统的数字人&#xff0c;英智知识图谱RAG型数字人提供…

.NET源码的在线探索:source.dot.net网站深度解析

一个在线的.NET源码查询网站为https://source.dot.net/。这个网站为开发者提供了便捷的.NET源码查询服务&#xff0c;无需从GitHub等代码托管平台下载整个源代码库&#xff0c;即可在线浏览和查询.NET框架及相关项目的源代码。 以下是该网站的一些主要功能特性&#xff1a; …

vue node node-sass sass-loader 版本 对应 与 兼容

警告&#xff1a; LibSass 和 Node Sass 已弃用。虽然它们将继续无限期地接收维护版本&#xff0c;但没有计划添加其他功能或与任何新的 CSS 或 Sass 功能兼容。仍在使用它的项目应该转移到 Dart Sass。 sass Sass是一种预处理器脚本语言&#xff0c;可以解释或编译成…

英语学习之vegetable

这里写目录标题 不熟悉的单词熟悉的句型 不熟悉的单词 brocolli &#xff08;花&#xff09;椰菜&#xff1b;西兰花&#x1f966; spinach 菠菜 aubergine 茄子&#x1f346; cabbage 卷心菜 zucchini/courgette 西葫芦 parsnip 欧洲防风&#xff0c;民间俗称“芹菜萝卜” …

[240917] Docker vs Podman:容器化的终极对决 | Kali Linux 2024.3 版本发布

目录 Docker &#x1f40b; vs &#x1f9ad; Podman: 容器化领域的终极对决一、容器化简介二、&#x1f40b; Docker 是什么&#xff1f;三、&#x1f9ad; Podman 是什么&#xff1f;四、架构差异1. Docker 的客户端-服务器模型2. Podman 的无守护进程架构 五、安全注意事项1…

苹果 2024 秋季新品发布会一文汇总:iPhone 16 / Pro 登场、手表耳机齐换代

✌ 作者名字&#xff1a;高峰君主 &#x1f4eb; 如果文章知识点有错误的地方&#xff0c;请指正&#xff01;和大家一起学习&#xff0c;一起进步&#x1f440; &#x1f4ac; 人生格言&#xff1a;没有我不会的语言&#xff0c;没有你过不去的坎儿。&#x1f4ac; &#x1f5…

2024年最新版本spss 27中文版新功能特色及下载安装激活教程

在智能设备领域&#xff0c;一款名为SPSS 27.0的专业统计软件正因其卓越性能和革新功能在市场上引发热议。这款老牌统计工具在历经多次迭代后&#xff0c;不仅保持了其在科研和教育领域的核心地位&#xff0c;还通过增强用户体验和引入先进功能&#xff0c;重新定义了数据分析的…

数据爬虫中遇到验证码的解决方法

在数据爬虫中遇到验证码是一个常见且复杂的问题&#xff0c;验证码的存在主要是为了阻止自动化工具&#xff08;如爬虫&#xff09;对网站进行过度访问或数据抓取&#xff0c;以保护网站的安全性和数据的准确性。 一、验证码的基本概念与类型 验证码&#xff08;CAPTCHA&…

Java的输入输出

秋招笔试很多都是要自己写输出输出的&#xff0c;所以对常见的整理一下&#xff0c;后续也会持续更新的~~~ 目录 1.java中的Scanner类 1.1next()方法和nextLine()方法的区别 1. next() 方法 示例 2. nextLine() 方法 示例 主要区别 使用场景 2.print类 3.常用的转换…

JAVA算法数据结构第一节稀疏矩阵

一、稀疏矩阵介绍&#xff1a; 稀疏矩阵是一种特殊类型的矩阵&#xff0c;其中大部分元素都是零。在处理这类矩阵时&#xff0c;如果仍然使用标准的矩阵存储方式&#xff08;即传统的二维数组&#xff09;&#xff0c;则会浪费大量的存储空间来保存零值。为了提高存储效率以及…

iptables部署使用

1、Iptables-server安装确认 [rootlocalhost ~]$ rpm -qa|grep iptables #查看安装iptables iptables-1.4.21-18.0.1.el7.centos.x86_64 [rootlocalhost ~]$ rpm -ql iptables #查看iptables相关文件 2、安装iptables-services [rootlocalhost ~]$ yum list all|grep iptab…

Ubuntu24.04 安装opencv4.10

Ubuntu24.04 安装opencv4.10 一、下载OpenCV二、更新系统&#xff0c;安装必要的包1、“E: unable to locate libjasper-dev"的解决方法2、没有公钥&#xff0c;无法验证下列签名 :NO_PUBKEY 的解决方法 三、配置&#xff0c;使用cmake工具1、新建build目录2、在build中&a…

【梯度下降|链式法则】卷积神经网络中的参数是如何传输和更新的?

【梯度下降|链式法则】卷积神经网络中的参数是如何传输和更新的&#xff1f; 【梯度下降|链式法则】卷积神经网络中的参数是如何传输和更新的&#xff1f; 文章目录 【梯度下降|链式法则】卷积神经网络中的参数是如何传输和更新的&#xff1f;1. 什么是梯度&#xff1f;2.梯度…

2024 VMpro 虚拟机中如何给Ubuntu Linux操作系统配置联网

现在这是一个联网的状态 可以在商店里面下载东西 也能ping成功 打开虚拟网络编辑器 放管理员权限 进行设置的更改 选择DNS设置 按提示修改即可 注意的是首选的DNS服务器必须是114.114.114.114 原因 这边刚刚去查了一下 114.114.114.114 是国内的IP地址 8.8.8.8 是国外的I…

MySQL record 05 part

外键 注意&#xff0c;外键所在的表一般被称为从表&#xff0c;被引用的表被称为主表。 直接删除主表会报错&#xff0c;因为主表被从表&#xff08;有外键的那个表&#xff09;所引用,所以&#xff0c;删除主表&#xff08;被引用数据的表&#xff09;之前&#xff0c;要先删除…

【宠物小精灵之收服(待更新)】

题目 代码 #include <bits/stdc.h> using namespace std; int f[1010][510]; int main() {int n, m, k;cin >> n >> m >> k;int c 0;for(int i 1; i < k; i){int cost, hp;cin >> cost >> hp;for(int j n; j > cost; j--){for(i…

EndnoteX9安装及使用教程

EndnoteX9安装及使用教程 一、EndNote安装 1.1 下载 这里提供一个下载链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1RlGJksQ67YDIhz4tBmph6Q 提取码&#xff1a;5210 解压完成后&#xff0c;如下所示&#xff1a; 1.2 安装 双击右键进行安装 安装比较简单…

ChatGPT有三个快捷指令和三个模式,你知道吗?

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…