【JavaEE初阶】volatile 关键字、wait 和 notify

目录

一、volatile 关键字

1、volatile 能保证内存可见性

2、volatile 不保证原子性

二、wait 和 notify 

 1、wait()方法

 2、notify()方法

3、notifyAll()方法

4、wait 和 sleep 的对比


一、volatile 关键字

1、volatile 能保证内存可见性

我们前面的线程安全文章中,分析引起线程不安全的原因,其中就有一个原因是可见性,若一个线程对一个共享变量的修改,不能让其他线程看到,则会引起线程安全问题。因此,我们就引入了volatile 关键字,volatile 修饰的变量,能够保证 "内存可见性"。

(这里的“工作内存”不是真正的内存,就像CPU寄存器。)

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

  • 改变线程 工作内存 中volatile变量副本的值
  • 将改变后的副本的值从 工作内存 刷新到 主内存
代码在读取 volatile 修饰的变量的时候:
  • 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 从工作内存中读取volatile变量的副本
我们在讨论内存可见性时说, 直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存),速度非 常快,但是可能出现数据不⼀致的情况。 加上 volatile,就强制读写内存,速度是慢了, 但是数据变的更准确了。

代码示例:

在这个代码中

  • 创建两个线程 t1 和 t2
  • t1 中包含⼀个循环, 这个循环以 flag == 0 为循环条件.
  • t2 中从键盘读入⼀个整数, 并把这个整数赋值给 flag
  • 预期当用户输入非 0 的值的时候, t1 线程结束.  
static class Counter {
     public int flag = 0;
}
public static void main(String[] args) {
     Counter counter = new Counter();
     Thread t1 = new Thread(() -> {
         while (counter.flag == 0) {
             // do nothing
         }
         System.out.println("循环结束!");
     });
     Thread t2 = new Thread(() -> {
         Scanner scanner = new Scanner(System.in);
         System.out.println("输⼊⼀个整数:");
         counter.flag = scanner.nextInt();
     });
     t1.start();
     t2.start();
}
// 执⾏效果
// 当用户输⼊⾮0值时, t1 线程循环不会结束. (这显然是⼀个 bug)

 这里t1线程循环并不会结束,这是因为 t1 读的是自己工作内存中的内容,当 t2 对 flag 变量进行修改,此时 t1 感知不到 flag 的变化。

如果给 flag 加上 volatile:
static class Counter {
   public volatile int flag = 0;
}
// 执⾏效果
// 当用户输⼊⾮0值时, t1 线程循环能够⽴即结束.

2、volatile 不保证原子性

volatile 和 synchronized 有着本质的区别,synchronized 能够保证原子性,volatile 保证的是内存可见 性。
这里可以用我们前面线程安全那一篇文章中的代码来证明,将 synchronized去掉,加上对count变量的 volatile 修饰。
public class ThreadDemo {
    private static volatile long count = 0;
    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(()->{
            for (int i = 1;i <= 500000;i++) {
                count++;
            }
        });
        Thread t2 = new Thread(()->{
            for (long i = 0;i < 500000;i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count= "+count);
    }
}

可见运行结果为:

 

此时,最终 count 的值仍然无法保证是 1000000。所以volatile 不保证原子性,volatile 保证的是内存可见性。

二、wait 和 notify 

由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知,但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。
就比如打球,球场上的每个运动员都是独立的 "执行流" ,可以认为是⼀个 "线程"。而完成⼀个具体的进攻得分动作,则需要多个运动员相互配合,按照⼀定的顺序执行⼀定的动作,线程 1 先 "传球",线程 2 才能 "扣篮"。

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

  • wait() / wait(long timeout):让当前线程进入等待状态
  • notify() / notifyAll():唤醒在当前对象上等待的线程

 注意:wait, notify, notifyAll 都是 Object 类的方法

 1、wait()方法

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

这里要注意,wait 要搭配 synchronized 来使用,脱离 synchronized 使用 wait 会直接抛出异常。

wait 结束等待的条件:
  • 其他线程调用该对象的 notify 方法
  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本,来指定等待时间)
  • 其他线程调用该等待线程的 interrupted 方法,导致 wait 抛出 InterruptedException 异常

代码示例:观察wait()方法使用

public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    synchronized (object) {
       System.out.println("等待中");
       object.wait();
       System.out.println("等待结束");
    }
}
这样在执行到 object.wait() 之后就⼀直等待下去,当然程序肯定不能一直这么等待下去了,这个时候就 需要使用到另外⼀个方法,唤醒的方法notify()。

 2、notify()方法

notify 方法是唤醒等待的线程的:
  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  • 如果有多个线程等待,则由线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到")
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁
代码示例:使用notify()方法唤醒线程
  • 创建 WaitTask 类, 对应⼀个线程, run 内部循环调用wait.
  • 创建 NotifyTask 类, 对应另⼀个线程, 在 run 内部调用一次 notify
  • 注意, WaitTask 和 NotifyTask 内部持有同⼀个 Object locker,WaitTask 和 NotifyTask 要想配合就需要搭配同一个 Object
public class ThreadDemo {
    static class WaitTask implements Runnable {
        private Object locker;
        public WaitTask(Object locker) {
            this.locker = locker;
        }
        @Override
        public void run() {
            synchronized (locker) {
                while (true) {
                    try {
                        System.out.println("wait 开始");
                        locker.wait();
                        System.out.println("wait 结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    static class NotifyTask implements Runnable {
        private Object locker;
        public NotifyTask(Object locker) {
            this.locker = locker;
        }
        @Override
        public void run() {
            synchronized (locker) {
                System.out.println("notify 开始");
                locker.notify();
                System.out.println("notify 结束");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(new WaitTask(locker));
        Thread t2 = new Thread(new NotifyTask(locker));
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

3、notifyAll()方法

notify方法只是唤醒某一个等待线程,使用notifyAll方法可以一次唤醒所有的等待线程。
范例:使用notifyAll()方法唤醒所有等待线程,在上面的代码基础上做出修改
  •  创建 3 个 WaitTask 实例,1 个 NotifyTask 实例.
static class WaitTask implements Runnable {
    // 代码不变
}
static class NotifyTask implements Runnable {
    // 代码不变
}
    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();
        t3.start();
        t4.start();
        Thread.sleep(1000);
        t2.start();
    }

此时可以看到,调用notify 只能唤醒⼀个线程

  • 修改 NotifyTask 中的 run 方法,把 notify 替换成 notifyAll 
public void run() {
        synchronized (locker) {
            System.out.println("notify 开始");
            locker.notifyAll();
            System.out.println("notify 结束");
        }
    }

此时可以看到,调用 notifyAll 能同时唤醒 3 个wait 中的线程。

注意:虽然是同时唤醒 3 个线程,但是这 3 个线程需要竞争锁,所以并不是同时执行,而仍然是有先有后的执行。
理解 notify 和 notifyAll:
notify 只唤醒等待队列中的一个线程,其他线程还是乖乖等着
notifyAll ⼀下全都唤醒,需要这些线程重新竞争锁

4、wait 和 sleep 的对比

其实理论上 wait 和 sleep 完全是没有可比性的,因为⼀个是用于线程之间的通信的,⼀个是让线程阻塞⼀段时间。
唯⼀的相同点就是都可以让线程放弃执行⼀段时间。
但我们还是要总结下:
  1. wait 需要搭配 synchronized 使用,sleep 不需要。
  2. wait 是 Object 的方法,sleep 是 Thread 的静态方法。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【物联网与大数据应用】Hadoop数据处理

Hadoop是目前最成熟的大数据处理技术。Hadoop利用分而治之的思想为大数据提供了一整套解决方案&#xff0c;如分布式文件系统HDFS、分布式计算框架MapReduce、NoSQL数据库HBase、数据仓库工具Hive等。 Hadoop的两个核心解决了数据存储问题&#xff08;HDFS分布式文件系统&#…

国产CPU计算平台选型指南

信创&#xff0c;这两年已经不是什么新鲜词了&#xff0c;随着29号文、79号文的实施落地&#xff0c;信创产品加速从党政走向八大关基行业。 八大行业中&#xff0c;金融、教育、电信、石油等企业步伐更大&#xff0c;很多企业已经从窗口业务、日常办公这类轻量应用场景&#…

什么是灯塔工厂?灯塔工厂的作用?

什么是灯塔工厂&#xff1f; "灯塔工厂"概念源于德国的工业4.0战略&#xff0c;又称“工业4.0示范工厂”或“标杆工厂”&#xff0c;代表工业领域顶级的智能制造能力。2018年&#xff0c;由世界经济论坛和麦肯锡共同推出。 灯塔工厂是通过数字化、网络化和智能化手…

LeetCode(43)快乐数【哈希表】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 快乐数 1.题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也…

pygame图像变换:缩放、旋转、镜像

文章目录 函数列表图像显示翻转缩放旋转 函数列表 pygame的transform中封装了一些基础的图像处理函数&#xff0c;列表如下 函数功能flip镜像scale缩放至新的分辨率scale_by根据因子进行缩放scale2x专业图像倍增器rotate旋转rotozoom缩放并旋转smoothscale平滑缩放smoothscal…

山西电力市场日前价格预测【2023-12-01】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-12-01&#xff09;山西电力市场全天平均日前电价为246.08元/MWh。其中&#xff0c;最高日前电价为402.66元/MWh&#xff0c;预计出现在17:45。最低日前电价为0.00元/MWh&#xff0c;预计出…

[cocos creator]EditBox,editing-return事件,清空输入框

需求&#xff1a; 监听EditBox&#xff0c;editing-return 回车事件&#xff0c;在输入框内点击回车后&#xff0c;发送内容&#xff0c;并清空输入框 问题&#xff1a; 设置node.getComponent(EditBox).string ; 没有效果 解决办法&#xff1a; //设置string 为空 this.v…

直击广州车展 | 远航汽车“卷”出新高度

第23届广州车展作为2023年汽车行业的年度收官之作&#xff0c;成为各大汽车厂商“秀肌肉”的绝佳舞台&#xff0c;22万平方米的开放展区内容纳了1132辆展车&#xff0c;包括全球首发车59辆、概念车20辆、新能源车469辆。 新能源汽车产业发展迅猛&#xff0c;得益于新能源车型在…

如何使用Windows自带的IIS服务搭建本地站点并远程访问

文章目录 1.前言2.Windows网页设置2.1 Windows IIS功能设置2.2 IIS网页访问测试 3. Cpolar内网穿透3.1 下载安装Cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5.结语 1.前言 在网上各种教程和介绍中&#xff0c;搭建网页都会借助各种软件的帮助&#xf…

去北京医院预约,需要医保卡号,但是社保卡不在身边,北京的医保卡号咋网上查询

目录 1 问题2 查询 1 问题 要去北京某一个医院预约挂号&#xff0c;预约的时候选择的医保&#xff0c;需要写医保卡号&#xff0c;但是自己的社保卡不在身边&#xff0c;怎么办 记住&#xff0c;医保卡号不是社保卡号&#xff0c;是不一样的 北京医保卡号是12位 2 查询 登陆这…

Adobe InCopy の Adobe InDesign 大联动

今天我们再来进行Adobe全家桶剩余几位的介绍~ Adobe InCopy是一款专业的文字编辑和校对软件&#xff0c;它是InDesign的附属软件&#xff0c;主要用于编辑、校对和协作文本内容。InCopy提供了一系列功能&#xff0c;使得编辑和校对文本变得更加简单和高效。在InCopy中&#xff…

MongoDB快速入门及其SpringBoot实战

MongoDB快速入门及其SpringBoot实战 MongoDB简介 MongoDB 是一个基于分布式文件存储的数据库。由 C 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 MongoDB是一个开源、高性能、无模式的文档型数据库&#xff0c;当初的设计就是用于简化开发和方便扩展&am…

配电室智慧运维监控系统

配电室智能运维监控系统是一个综合性的管理系统&#xff0c;专门针对配电室的运维工作进行设计。依托电易云-智慧电力物联网&#xff0c;它融合了先进的监测技术、自动化技术、数据分析技术等&#xff0c;对配电室进行全方位、实时的智能化监控和管理&#xff0c;以提升配电室运…

【C++】赋值运算符重载

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

解决noauth authentication required异常

今天在使用redis数据库的时候&#xff0c;突然给报了个这个错误&#xff0c;上网一查才知道是因为 Redis 服务器需要密码进行身份验证&#xff0c;因此&#xff0c;我们需要通过auth password 进行身份验证。不过我这个密码还是试了很多次才想起来的&#xff0c;哦好像是听网课…

2023.11.29 关于 MyBatis resultMap 和 多表查询

目录 resultType 和 resultMap 多表查询 resultType 和 resultMap 在 MyBatis 中二者被用于设置查询后所返回的数据类型 resultType 大多数情况下均可使用 resultType 进行设置返回数据类型 实例理解 下图为数据库中的一个 user 表&#xff0c;该 user 表包含四个字段 为了能…

7-1 哈夫曼树与哈夫曼编码

哈夫曼树与哈夫曼编码 题目描述输入格式输出格式输入样例输出样例 分数 30 作者 伍建全 单位 重庆科技学院 题目描述 哈夫曼树(Huffman Tree)又称最优二叉树&#xff0c;是一种带权路径长度最短的二叉树。所谓树的带权路径长度&#xff0c;就是树中所有的叶结点的权值乘上其到…

米贸搜|如何用Facebook为eBay实现引流?

要利用Facebook为eBay实现引流&#xff0c;可以尝试以下方法&#xff1a; 创建专页或社群&#xff1a;在Facebook上创建一个专页或社群&#xff0c;专注于你在eBay上销售的产品或相关主题。确保专页或社群的名称和描述清楚地表明与eBay有关。 定期发布内容&#xff1a;在Face…

MySQL数据库的安装

MySQL官网&#xff1a;https://www.mysql.com/ 进入下载页面&#xff1a;https://www.mysql.com/downloads/ 选择社区版&#xff1a; 选择MySQL Community Server&#xff1a; 根据自己的需要选择版本。例如选择8.2.0版本&#xff1a; 例如选择Windows (x86, 64-bit), M…