volatile,wait,notify关键字

news/2024/11/28 22:37:12/

在这里插入图片描述

文章目录

  • 一、volatile关键字
  • 二、wait 和 notify
    • wait
    • notify
    • notifyAll
    • wait 和 sleep 的区别
    • 顺序打印ABC

一、volatile关键字

volatile关键字的存在是用来解决内存可见性问题的。
在这里插入图片描述
我在 :线程安全问题 这篇文章中介绍过内存可见性问题。
前面我们讨论内存可见性时说了, 直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存), 速度
非常快, 但是可能出现数据不一致的情况.

public class Test {static int flg = 0;public static void main(String[] args) {Scanner scan = new Scanner(System.in);Thread t1 = new Thread(() -> {while(flg == 0) {}System.out.println("t1循环结束");});Thread t2 = new Thread(() -> {System.out.println("输入一个整数");flg = scan.nextInt();});t1.start();t2.start();}
}

在这里插入图片描述
当我们输入1时,t1线程并没有循环结束,我们可以打开jconsle,观看一下线程信息
在这里插入图片描述
我们可以发现此时t2线程已经执行完成了,但我们的t1仍然在执行。
在这里插入图片描述
我们可以分析一下t1线程所进行的操作,flg == 0需要进行两步操作:

  1. load 把主内存的flg值读取到工作内存
  2. cmp 将工作内存的值与0相比较,决定程序执行。

我们t1线程在t2线程修改flg值之前,load的结果都是一样的,而且load 的 速度与cmp操作相比,速度慢了很多,于是我们JVM就会大胆的认为没人改flg值了,于是不再重复load了。

正是因为这样的误判存在,所以volatile关键字的作用就来了,我们可以给变量flg加上volatile关键字,就是告诉编译器,每次都得load这个变量,可能会发生改变。

在这里插入图片描述
在这里插入图片描述
我们可以发现,当我们加上volatile关键字之后,程序达到了我们预期的效果。
我们上述所看到的内存可见性 编译器优化的问题 是有发生的可能,但也不是百分百出现的。
在这里插入图片描述
在这里插入图片描述
我们在循环的时候,代码稍加改写,加入sleep,让循环执行慢一点,也达到了预期的效果,但这也存在不确定性,最稳妥的做法就是对这些可能存在内存不可见问题的变量加上volatile关键字,volatile保证数据准确的同时,执行速度也会有所下降。
volatile不能保证原子性,原子性靠synchronized保证

二、wait 和 notify

由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.
我们之前学习的join,sleep也能达到部分的目的,但是join,sleep太过于死板,不够灵活,join必须等t1执行完毕,t2才能执行。sleep必须得指定一个时间,但线程执行的时间我们人为不好估计。
wait 和 notify 可以很好的解决上述问题。
注意: wait, notify, notifyAll 都是 Object 类的方法.

wait

wait使该线程进行阻塞等待
在这里插入图片描述
一样的,我们在使用wait的使用需要抛一个中断异常。

public static void main(String[] args) throws InterruptedException {Object o = new Object();o.wait();}

在这里插入图片描述
我们可以发现报了一个非法锁异常,要想理解这个异常我们需要明白wait所做的事情都是什么:

  1. 释放锁
  2. 进行阻塞等待(等待队列)
  3. 满足条件后,重新尝试获取锁。

现在我们就可以理解这个异常了,我们都没锁,何谈释放锁,就好比我们是单身状态,何谈分手之言。
所以,wait 需要和 synchronized搭配使用的

public static void main(String[] args) throws InterruptedException {Object o = new Object();synchronized (o) {System.out.println("wait前");o.wait();System.out.println("wait后");}}

在这里插入图片描述
在这里插入图片描述
怎么唤醒呢?下面我们来了解一下notify关键字

notify

notify 方法用来唤醒wait等待的线程.
该方法同样的,必须在synchronized(obj){}代码块内部使用

public static void main(String[] args) {Object o = new Object();Thread t1 = new Thread(() -> {System.out.println("wait前");try {synchronized (o) {o.wait();}} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t2 = new Thread(() -> {System.out.println("notify之前");synchronized (o) {o.notify();}System.out.println("notify之后");});t1.start();t2.start();}

在这里插入图片描述
我们使用notify的对象和使用wait的对象必须相同,因为notify只能唤醒同一对象上等待的线程。
在这里插入图片描述
为什么我们这里的结果和我们的预期有些差异
在这里插入图片描述
当我们启动t1,t2线程时,由于线程调度是随机的,不能够保证t1,t2的执行顺序,如果先执行了t2的notify,后执行的t1的wait,此时notify不会起任何意义的,但也对程序没什么坏的影响。
所以我们在使用wait 和 notify一定要保证他们的执行顺序。
在这里插入图片描述
我们可以加一个sleep,让t1线程的wait启动后,再去执行t2现在的notify,当然这个sleep的时间不确定,具体取决于线程调度的时间。
在这里插入图片描述
但要是我们仍害怕notify先执行了,wait就只能死等下去了,那么我们可以wait带参数版本

方法作用
wait()死等
wait(long miles)执行等待最大时间

注意:
1.如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
2.在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

notifyAll

notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
在这里插入图片描述
notify就相当于我们宿舍的卫生间,自己出来之后,随机告诉一个舍友你现在可以去了。
notify 只唤醒等待队列中的一个线程. 其他线程还是处于wait状态
在这里插入图片描述
notifyAll相当于,同时向宿舍的所有舍友喊我出来了,你们可以进去了。
notifyAll 一下全都唤醒, 需要这些线程重新竞争锁

wait 和 sleep 的区别

相同点: sleep()和wait()都可以暂停线程的执行。
不同点:
1.wait是Object的方法,sleep是Thread的静态方法
2. wait 需要搭配 synchronized 使用. sleep 不需要.
3. sleep()方法睡眠指定时间之后,线程会自动苏醒。wait()方法被调用后,可以通过notify()或notifyAll()来唤醒wait的线程。
4. notify唤醒wait是正常的,interrput中断sleep是异常的
5. sleep()常用于一定时间内暂停线程执行,wait()常用于线程间交互和通信。

顺序打印ABC

有三个线程,分别只能打印ABC,三个线程按照ABC的顺序打印。

public static void main(String[] args) throws InterruptedException {Object lock1 = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {System.out.println("A");synchronized (lock1) {lock1.notify();}});Thread t2 = new Thread(() -> {try {synchronized (lock1) {lock1.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("B");synchronized (lock2) {lock2.notify();}});Thread t3 = new Thread(() -> {try {synchronized (lock2) {lock2.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("C");});t2.start();t3.start();Thread.sleep(100);t1.start();}

在这里插入图片描述
这里线程启动的顺序为什么是这样,因为我们在t1的notify执行之前,必须保证t2,t3的wait启动。


http://www.ppmy.cn/news/3369.html

相关文章

识破贷后资金归集——关联网络

近几年,金融机构为了扩大信贷规模,抢占市场份额,通过贷款将贷款发放给无法直接通过金融机构获得贷款的个人或者企业,但这也给金融机构带来了多重风险。 首先,我们来看下资金归集是什么。所谓资金归集,是银…

Mapbox 与 Babylon.js 可视化 glsl 特效篇(三十六)

我决定不从Babylonjs 基础来讲了 直接整合mapbox与babylonjs可视化来讲 我整合一个类库 后续不断更新中 npm i haibalai/mapbox-babylonjs 初始化mapbox-babylonjs 类库, map 是mapbox.gl 的map 对象 import { BabylonMapManager } from “haibalai/mapbox-baby…

为什么mac电脑识别不出来u盘?macbook识别不了u盘的解决办法

为什么mac电脑识别不出来u盘?关于U盘插入Mac电脑无反应的情况有很多种,是电脑无法识别U盘?电脑上面没有U盘的图标?还是插入后无法对U盘进行写入?针对不同的情况,解决的方法也是不一样的。现在,我…

STM32CUBEMX开发GD32F303(17)----内部Flash读写

概述 本章STM32CUBEMX配置STM32F103,并且在GD32F303中进行开发,同时通过开发板内进行验证。 本例程主要讲解如何对芯片自带Flash进行读写,用芯片内部Flash可以对一些需要断电保存的数据进行保存,无需加外部得存储芯片&#xff0c…

拓扑排序(数据结构之图的应用)

我们先搞清楚一个概念: 什么是出度与入度? 在有向图中,箭头是具有方向的,从一个顶点指向另一个顶点,这样一来,每个顶点被指向的箭头个数,就是它的入度。从这个顶点指出去的箭头个数&#xff0c…

WPF入门 第一篇 基础布局与简单样式

基础布局与简单样式 首先&#xff0c;创建WPF项目&#xff0c;在自动打开的MainWindow.xaml里面&#xff0c;找到Grid标签&#xff0c;并将它替换为&#xff1a; <Grid><Grid.RowDefinitions><RowDefinition></RowDefinition><RowDefinition>&…

Adobe Acrobat 图标异常的解决办法

今天使用 Adobe Acrobat 打开文件阅读时&#xff0c;发现底部任务栏的图标是这样的&#xff0c;如下图所示。 这可不是常见的 Adobe Acrobat 图标&#xff0c;肯定是哪里出了问题&#xff0c;于是我在电脑开始这里找到 Adobe Acrobat 的快捷方式&#xff0c;其图标也是这样的&…

pikachu靶场-8 越权漏洞

越权漏洞 越权漏洞概述 由于没有对用户权限进行严格的判断&#xff0c;导致低权限的账号&#xff08;比如普通用户&#xff09;可以去完成高权限账号&#xff08;比如超级管理员&#xff09;范围内的操作 平行越权&#xff1a;A用户和B用户属于同一级别用户&#xff0c;但各…