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.rhkb.cn/news/426190.html

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

相关文章

国家标准参编周期一般是多久?参编的流程有哪些?

在当今快速发展的时代&#xff0c;标准的重要性日益凸显。国家标准作为规范行业发展、保障产品质量、促进技术进步的重要依据&#xff0c;吸引着众多企业积极参与其中。而国家标准参编&#xff0c;为企业提供了一个提升自身竞争力、展示技术实力的良好平台。那么&#xff0c;国…

第k个排列 - 华为OD统一考试(E卷)

2024华为OD机试&#xff08;E卷D卷C卷&#xff09;最新题库【超值优惠】Java/Python/C合集 题目描述 给定参数n&#xff0c;从1到n会有n个整数:1,2,3,.,n&#xff0c;这n个数字共有 n!种排列。按大小顺序升序列出所有排列情况&#xff0c;并-一标记&#xff0c;当n3时,所有排列…

vscode任务配置之tasks.json

目录 用途说明 用途1&#xff1a;配置编译任务 1.生成task.json文件 2.编辑task.json文件 3.运行任务 用途2&#xff1a;给一个脚本文件配置任务 1.生成task.json文件 2.编辑task.json文件 3.运行任务 用途说明 在VS Code中配置任务主要涉及到task.json文件的编辑&am…

Java学习Day42:骑龙救!(springMVC)

springMVC与sevlet都是对应表现层web的&#xff0c;但是越复杂的项目使用SpringMVC越方便 基于Java实现MVC模型的轻量级web框架 目标&#xff1a; 小案例&#xff1a; 1.导入依赖 spring-context: 提供 Spring 框架的核心功能&#xff0c;如依赖注入、事件发布和其他应用上…

面试真题-TCP的三次握手

TCP的基础知识 TCP头部 面试题&#xff1a;TCP的头部是多大&#xff1f; TCP&#xff08;传输控制协议&#xff09;的头部通常是固定的20个字节长&#xff0c;但是根据TCP选项&#xff08;Options&#xff09;的不同&#xff0c;这个长度可以扩展。TCP头部包含了许多关键的字…

ollama安装(ubuntu20.04)

Ollama是一款开源的自然语言处理工具&#xff0c;它可以帮助开发者快速构建文本处理应用。 ollama官网: https://ollama.ai/ 一、ollama 自动安装 linux统一采用sh脚本安装&#xff0c;一个命令行搞定。 curl -fsSL https://ollama.com/install.sh | sh二、ollama 手动安装 o…

ros学习笔记.4 Path Planning Part 2 (避障)

避障是如何工作的什么是局部规划器&#xff1f;什么是局部成本图&#xff1f;路径规划回顾如何使用动态重新配置和其他 Rviz 工具 局部规划器 一旦全局规划器计算出要遵循的路径&#xff0c;该路径就会发送给局部规划器。然后&#xff0c;局部规划器将执行全局规划的每个部分&…

spring中对于servlet API的封装---springWeb

目录 一.springweb概述 二.springweb的特点 三.springweb的运行流程 四.springweb组件 五.springweb的搭建 1.导包 2.配置 DispatcherServlet 3.开启 springweb 注解 4.处理器的搭建 六.springweb注解 七.springweb拦截器 1.拦截器概述 2.拦截器的实现 (1)添加 servelt api 依赖…

详解:冒泡排序

1.是什么 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法。它重复地遍历要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换&#xff0c;也就是说该数列已经排序完成…

望繁信科技携流程智能解决方案亮相CNDS 2024新能源产业数智峰会

9月13日&#xff0c;CNDS 2024中国新能源产业数智峰会在北京圆满落幕。本次峰会以“走向数字新能源”为主题&#xff0c;汇聚了来自新能源领域的顶尖领袖、专家学者及知名企业代表&#xff0c;共同探讨数字化技术在新能源行业中的创新应用和发展趋势。上海望繁信科技有限公司&a…

C++ List (带你一篇文章搞定C++中的List类)

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 数据结构习题_LaNzikinh篮子的博客-CSDN博客 初阶数据结构_LaNzikinh篮子的博客-CSDN博客 收入专栏&#xff1a;C_LaNzikinh篮子的博客-CSDN博客 其他专…

flask项目初始化

1、初始环境 python3.8 2、flask文档地址&#xff1a;https://flask.palletsprojects.com/en/latest/installation/#install-flask 3、初始化项目 $ mkdir myproject $ cd myproject $ python3 -m venv .venv $ . .venv/bin/activate $ pip install Flask4、打开项目mypr…

Pycharm出现Please specify a different SDK name报错,但是看不到重名环境解决方案

这句话的意思是出现了重名的环境 &#xff0c;一般情况下删除重名的环境即可解决问题。做法如下图所示 1&#xff0c;点击右上角齿轮→settings&#xff08;或者File→settings&#xff09;进入Python Interpreter 2.点击这个沙漏按键&#xff0c;你会发现多了几个环境&#x…

VScode相关问题与解决

VScode只是一个文档编辑器&#xff0c;类似于我们使用的记事本。我们在编辑完文档之后呢一定要保存。 文档编辑器加上它可以安装不同的插件&#xff0c;就可以进行程序开发。 1.写c文件时找不到头文件stdio.h 在linux下我们gcc命令来编译c文件时&#xff0c;会遇到找不到头文…

一文讲懂Mac中的环境变量

你是否曾经因为环境变量配置不当而浪费了宝贵的开发时间?你是否好奇为什么有时候在终端输入命令会提示"command not found",而有时候又能正常运行?如果你是一名Mac用户,并且希望真正掌握环境变量的奥秘,那么这篇文章将为你揭开Mac中环境变量的神秘面纱,帮助你成为一…

Java设计模式—面向对象设计原则(二) --------> 里氏代换原则 LSP (完整详解,附有代码+案列)

文章目录 里氏代换原则3.2.1 概述3.2.2 改进上述代码 里氏代换原则 里氏代换原则&#xff1a;Liskov Substitution Principle&#xff0c;LSP 3.2.1 概述 里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则&#xff1a;任何基类可以出现的地方&#xff0c;子类一定…

html实现好看的多种风格手风琴折叠菜单效果合集(附源码)

文章目录 1.设计来源1.1 风格1 -图文结合手风琴1.2 风格2 - 纯图片手风琴1.3 风格3 - 导航手风琴1.4 风格4 - 双图手风琴1.5 风格5 - 综合手风琴1.6 风格6 - 简描手风琴1.7 风格7 - 功能手风琴1.8 风格8 - 全屏手风琴1.9 风格9 - 全屏灵活手风琴 2.效果和源码2.1 动态效果2.2 源…

FloodFill(洪水灌溉)算法专题——DFS深搜篇

目录 1、图像渲染 1.1 算法原理 1.2 算法代码 2、岛屿数量 2.1 算法原理 2.2 算法代码 3、岛屿的最大面积 3.1 算法原理 3.2 算法代码 4、被围绕的区域 4.1 算法原理 4.2 算法代码 5、太平洋大西洋水流问题 5.1 算法原理 5.2 算法代码 6、扫雷游戏 6.1 算法原理…

React学习day07-ReactRouter-抽象路由模块、路由导航、路由导航传参、嵌套路由、默认二级路由的设置、两种路由模式

14、ReactRouter续 &#xff08;2&#xff09;抽象路由模块 1&#xff09;新建page文件夹&#xff0c;存放组件 组件内容&#xff1a; 2&#xff09;新建router文件夹&#xff0c;在其下创建实例 3&#xff09;实例导入&#xff0c;使用 4&#xff09;效果 &#xff08;3&…

【大数据方案】智慧大数据平台总体建设方案书(word原件)

第1章 总体说明 1.1 建设背景 1.2 建设目标 1.3 项目建设主要内容 1.4 设计原则 第2章 对项目的理解 2.1 现状分析 2.2 业务需求分析 2.3 功能需求分析 第3章 大数据平台建设方案 3.1 大数据平台总体设计 3.2 大数据平台功能设计 3.3 平台应用 第4章 政策标准保障体系 4.1 政策…