当前位置: 首页 > news >正文

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 的静态方法 .

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 知识|智能网联汽车多域电子电气架构会如何发展?
  • unity 图片置灰shader
  • Datawhale------Tiny-universe学习笔记——Qwen(1)
  • 算法——贡献法
  • k8s 微服务 ingress-nginx 金丝雀发布
  • 几种修改docker默认存储位置的方法
  • Linux:RPM软件包管理以及Yum软件包仓库
  • Leetcode—环形链表||
  • 下载chromedriver驱动
  • openmv与stm32通信
  • 面试经典150题——多数元素
  • 基于深度学习的因果推理与决策
  • AI+RPA 实战揭秘:DrissionPage 助力 CSDN 热榜数据抓取与 AI 结合
  • 跨界融合,GIS如何赋能游戏商业——以《黑神话:悟空》为例
  • 2024最新版,人大赵鑫老师《大语言模型》新书pdf分享
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • 【Linux系统编程】快速查找errno错误码信息
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • bootstrap创建登录注册页面
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • Centos6.8 使用rpm安装mysql5.7
  • golang 发送GET和POST示例
  • Java教程_软件开发基础
  • js作用域和this的理解
  • Linux快速复制或删除大量小文件
  • Map集合、散列表、红黑树介绍
  • PHP的类修饰符与访问修饰符
  • Quartz初级教程
  • SegmentFault 2015 Top Rank
  • vue 个人积累(使用工具,组件)
  • vuex 学习笔记 01
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 电商搜索引擎的架构设计和性能优化
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 关于extract.autodesk.io的一些说明
  • 警报:线上事故之CountDownLatch的威力
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 聊一聊前端的监控
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 温故知新之javascript面向对象
  • 优化 Vue 项目编译文件大小
  • 正则表达式
  • Hibernate主键生成策略及选择
  • MPAndroidChart 教程:Y轴 YAxis
  • 正则表达式-基础知识Review
  • ​比特币大跌的 2 个原因
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • # 数论-逆元
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (十)Flink Table API 和 SQL 基本概念
  • (贪心 + 双指针) LeetCode 455. 分发饼干
  • (转) ns2/nam与nam实现相关的文件