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

【JavaEE初阶】多线程(三)volatile wait notify关键字 单例模式

摄影分享~~
在这里插入图片描述

文章目录

  • volatile关键字
    • volatile能保证内存可见性
  • wait和notify
    • wait
    • notify
    • notifyAll
    • wait和sleep的区别
    • 小练习
  • 多线程案例
    • 单例模式
      • 饿汉模式
      • 懒汉模式

volatile关键字

volatile能保证内存可见性

import java.util.Scanner;class MyCounter {public int flag = 0;
}public class ThreadDemo14 {public static void main(String[] args) {MyCounter myCounter = new MyCounter();Thread t1 = new Thread(() -> {while (myCounter.flag == 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1 循环结束");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("请输入一个整数: ");myCounter.flag = scanner.nextInt();});t1.start();t2.start();}
}

以上代码运行的结果可能是输入1后,t1这个线程并没有结束。而是一直在while中循环。而t2线程已经执行完了。
以上情况,就叫做内存可见性问题
在这里插入图片描述
这里使用汇编来理解,大概分为两步操作:

  1. load,把内存中flag的值,读到寄存器中。
  2. cmp,把寄存器中的值,和0进行比较。根据比较结果,决定下一步往哪个地方执行(条件跳转指令)

上述循环循环体为空,循环执行速度极快。循环执行很多次,在t2真正修改之前,load得到的结果都是一样的。另一方面,load操作和cmp操作相比,速度慢的多得多。由于load执行速度太慢(相比于cmp),再加上反复load到的结果都是一样的,JVM就做出了一个大胆的决定:不再真正的重复load,判定没有人修改flag值(但实际上是有人在修改的,t2在修改),直接就读取一次就好。(编译器优化的一种方式)
内存可见性问题:一个线程针对一个变量进行读取操作,同时另一个线程针对这个变量进行修改。此时读到的值,并不一定是修改之后的值。(jvm/编译器在多线程环境下优化时残生了误判)
此时,我们就需要手动干预了。我们可以给flag这个变量加上volatile关键字。告诉编译器,这个变量是“易变”的,需要每一次都重新读取这个变量的内容。
在这里插入图片描述
volatile不保证原子性,原子性是由synchronized来保证的。

wait和notify

举个列子:
t1,t2两个线程,希望t1先执行任务,任务执行快结束了让t2来干,就可以让t2先wait(阻塞,主动放弃cpu)。等t1任务执行快结束了,在通过notify通知t2,把t2唤醒,让t2开始执行任务。
上述场景中,使用join和sleep可以吗?
使用join,必须要t1彻底执行完,t2才能执行。如果希望t1执行一半任务然后让t2执行,join无法完成。
使用sleep,必须制定一个休眠时间,但是t1执行任务的时间是难以估计的。
使用wait和notify可以解决上述问题。

wait

wait进行阻塞,某个线程调用wait方法,就会进入阻塞,此时就处于WAITING.
在这里插入图片描述

这个异常,很多带有阻塞功能的方法都带,这些方法都是可以被interrupt方法通过以上异常唤醒。
我们再来看一个代码:

public class ThreadDemo17 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("执行完毕!");});t.start();System.out.println("wait前");t.wait();System.out.println("wait后");}
}

在这里插入图片描述
这里会出现非法的锁状态异常。锁的状态一般是被加锁的状态,被解锁的状态。
为什么会出现这个异常呢?和wait的操作有关:

wait的操作:

  1. 先释放锁
  2. 进行阻塞等待
  3. 收到通知后,重新尝试获取锁,并且在获取锁后,继续往下执行。

上述代码没有锁就想要释放锁,所以出现了非法的锁状态异常。
因此,wait操作要搭配synchronized来使用。
在这里插入图片描述

notify

wait和notify一般搭配使用。notify方法用来唤醒wait等待的线程, wait能够释放锁, 使线程等待, 而notify唤醒线程后能够获取锁, 然后使线程继续执行。
在这里插入图片描述
如果上述代码中,t1还没有执行wait,t2已经执行了notify,那么此时的声明就是没有用的。t2执行notify后,t1执行wait后会一直阻塞等待。
注意上述代码在t2唤醒t1之后,t1和t2之间的执行是随机的,也是就标号3和标号4的地方的顺序是不确定的。

方法效果
wait();无参数,一直等直到notify唤醒
wait(时间参数);指定最长等待时间

notifyAll

notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
一般情况下,使用notify。因为全部唤醒会导致线程之间抢占式执行。不一定安全。

wait和sleep的区别

相同点:

  • 都可以使线程暂停一段时间来控制线程之间的执行顺序.
  • wait可以设置一个最长等待时间, 和sleep一样都可以提前唤醒.

不同点:

  • wait是Object类中的一个方法, sleep是Thread类中的一个方法.
  • wait必须在synchronized修饰的代码块或方法中使用, sleep方法可以在任何位置使用.
  • wait被调用后当前线程进入BLOCK状态并释放锁,并可以通过notify和notifyAll方法进行唤醒;sleep被调用后当前线程进入TIMED_WAITING状态,不涉及锁相关的操作.
  • 使用sleep只能指定一个固定的休眠时间, 线程中执行操作的执行时间是无法确定的; 而使用wait在指定操作位置就可以唤醒线程.
  • sleep和wait都可以被提前唤醒, interruppt唤醒sleep, 是会报异常的, 这种方式是一个非正常的执行逻辑; 而noitify唤醒wait是正常的业务执行逻辑, 不会有任何异常.

小练习

有三个线程, 分别只能打印 A, B, C. 控制三个线程固定按照 ABC 的顺序来打印.

public class ThreadDemo18 {// 有三个线程, 分别只能打印 A, B, C. 控制三个线程固定按照 ABC 的顺序来打印.public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{System.out.println("A");synchronized (locker1) {locker1.notify();}});Thread t2 = new Thread(()->{synchronized (locker1) {try {locker1.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("B");synchronized (locker2) {locker2.notify();}});Thread t3 = new Thread(()->{synchronized (locker2) {try {locker2.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("C");});t2.start();t3.start();Thread.sleep(100);t1.start();}
}

创建locker1,供1,2使用
创建locker2,供2,3使用
线程3,locker2.wait()
线程2, locker1.wait()唤醒后执行locker2.notify
线程1执行自己的任务,执行完后locker.notify
在这里插入图片描述

多线程案例

单例模式

单例模式是设计模式的一种。
单例模式能保证某个类在程序中只存在唯一一份的实例,而不会创建出多个实例。
单例模式具体的实现方式分为“饿汉”和“懒汉”。

饿汉模式

类加载的同时,创建实例。
类对象在一个java进程中,只有一份。因此类对象内部的类属性也是唯一的。
在类加载阶段,就把实例创建出来了。

//饿汉模式的单例模式的实现
//保证Singleton这个类只能创建出一个实例
class Singleton{//在此处,先将实例创建出来private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}//为了避免Singleton类不小心被多复制出来//把构造方法设为private,在类外,无法通过new的方式来创建一个Singletonprivate Singleton(){}
}
public class ThreadDemo19 {public static void main(String[] args) {Singleton s = Singleton.getInstance();Singleton s2 = Singleton.getInstance();//Singleton s3 = new Singleton();System.out.println(s == s2);}
}

在这里插入图片描述
在这里插入图片描述

  • static保证这个实例唯一
  • static保证这个实例被创建出来。

懒汉模式

class SingletonLazy{private static SingletonLazy instance = null;public static SingletonLazy getIsntance() {if(instance == null){instance = new SingletonLazy();}return instance;}
}
public class ThreadDemo20 {public static void main(String[] args) {SingletonLazy s = SingletonLazy.getIsntance();SingletonLazy s2 = SingletonLazy.getIsntance();System.out.println(s == s2);}
}

在这里插入图片描述

在多线程中调用instance,饿汉模式是线程不安全的。
在这里插入图片描述
那么如何保证懒汉模式线程安全呢?
**加锁。**线程安全的本质问题,就是读,比较,写这三个操作不是原子的。所以我们可以加锁来解决线程安全问题。
在这里插入图片描述

但是,加锁操作就导致每次调用getInstance都需要花一定的开销。而我们的加锁只针对new对象之前,所以我们就可以判断一下对象是否创建,再去决定加锁。
如果对象创建了,就不加锁。如果对象没有创建,就加锁。
在这里插入图片描述
上述代码还存在一个问题,即内存可见性问题:
假如调用getInstance的线程有很多,此时代码就有可能被优化(第一次读内存,后续读的是寄存器/cache)
除此之外,可能还会涉及到指令重排序。
在这里插入图片描述
上述代码中,分为三个步骤:

  1. 申请内存空间
  2. 调用构造方法,把这个内存空间初始化成一个对象
  3. 把内存空间的地址赋值给instance引用

而编译器的指令重排序操作就会调整代码执行顺序,123可能会变成132.(单线程中没有影响)
我们可以给代码中加上volatile。
volatile有两个功能:

  1. 解决内存可见性
  2. 禁止指令重排序。

以下为懒汉模式的单例模式的完整代码:

class SingletonLazy{private volatile static SingletonLazy instance = null;public static SingletonLazy getInstance() {if(instance ==null){synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}}  return instance;}
}
public class ThreadDemo20 {public static void main(String[] args) {SingletonLazy s = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s == s2);}
}

相关文章:

【JavaEE初阶】多线程(三)volatile wait notify关键字 单例模式

摄影分享~~ 文章目录 volatile关键字volatile能保证内存可见性 wait和notifywaitnotifynotifyAllwait和sleep的区别小练习 多线程案例单例模式饿汉模式懒汉模式 volatile关键字 volatile能保证内存可见性 import java.util.Scanner;class MyCounter {public int flag 0; }p…...

git把一个分支上的某次修改同步到另一个分支上,并解决git cherry-pick 冲突

背景 我们在工作中经常会碰到好几个同事共同在一个分支上开发,一个项目同时会有好几个分支,我们在feature上开发的功能时,有可能某个需求需要提前发布,这个时候我们已经在feature A上开发完成,但是需要在master上发布…...

S32K3系列单片机开发笔记(SIUL是什么/配置引脚复用的功能·)

前言 今天花时间看了一下,SIUL2模块的相关内容,并参照文档,以及例程作了一些小记录,知道该如何使用这个外设,包括引脚的配置,中断配置,以及常用函数的使用等,但对其中的一些细节还需…...

Linux没网络的情况下快速安装依赖或软件(挂载本地yum仓库源(Repository))

一、上传iso系统镜像: 上传和系统同一版本、同一位数(32bit:i686或i386/64bit:x86_64)的系统,不能是Minimal版本,可以是DVD(较全)或everything(最全)。 注&am…...

为了安装pip install pyaudio花费不少时间,坑

记录一下吧: 环境: mac OS Monterey 12.6.5 pyaudio是没有mac下的whl, 通过pip安装是需要进行编译的,我原来系统的是/usr/local/bin/clang 15.0.0版本,然后调用的C_CLUDE_PATH就是/usr/local/include和下面的c/v1&am…...

第十一章 组合模式

文章目录 前言一、组合模式基本介绍二、UML类图三、完整代码抽象类,所有类都继承此类学校类以父类型引用组合一个学院类学院类以父类型引用组合一个专业类专业类,叶子节点,不能再组合其他类测试类 四、组合模式在JDK集合的源码分析五、组合模…...

LeetCode链表OJ题目 代码+思路分享

目录 删除有序数组中的重复项合并两个有序数组移除链表元素 删除有序数组中的重复项 链接: link 题目描述: 题目思路: 本题使用两个指针dst和src一前一后 相同情况: 如果nums[dst]nums[src],那么src 不相同情况: 此…...

第06讲:为何各大开源框架专宠 SPI 技术?

在此前的内容中,已经详细介绍了 SkyWalking Agent 用到的多种基础技术,例如,Byte Buddy、Java Agent 以及 OpenTracing 中的核心概念。本课时将深入介绍 SkyWalking Agent 以及 OAP 中都会使用到的 SPI 技术。 JDK SPI 机制 SPI&#xff08…...

[Unity] No.1 Single单例模式

单例模式 1. 基础 定义:单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地…...

【chatGPT知识分享】Flutter web 性能优化基础入门

简介 Flutter 是 Google 在这里插入代码片公司推出的跨平台移动应用开发框架,支持 Android、iOS 和 Web 等多个平台。Flutter Web 是 Flutter 在 Web 平台上的应用,可以开发具有良好用户体验的网站。但是,由于 Web 环境的特殊性,…...

探索Qt折线图之美:一次详尽的多角度解析

探索Qt折线图之美:一次详尽的多角度解析 第一章:Qt折线图的基本概念与应用场景(Basic Concepts and Applications of Qt Line Charts)1.1 Qt折线图简介(Introduction to Qt Line Charts)1.2 Qt折线图的应用…...

minio集群部署,4台服务器+1台nginx

4台主机1台nginx负载均衡 分布式Minio里所有的节点需要有同样的access秘钥和secret秘钥,即:用户名和密码 分布式Minio存放数据的磁盘目录必须是空目录 分布式Minio官方建议生产环境最少4个节点,因为有N个节点,得至少保证有N/2的节…...

实例分割算法BlendMask

实例分割算法BlendMask 论文地址:https://arxiv.org/abs/2001.00309 github代码:https://github.com/aim-uofa/AdelaiDet 我的个人空间:我的个人空间 密集实例分割 ​ 密集实例分割主要分为自上而下top-down与自下而上bottom-up两类方法…...

多线程、智能指针以及工厂模式

目录 一、unique_lock 二、智能指针 (其实是一个类) 三、工厂模式 一、unique_lock 参考文章【1】,了解unique_lock与lock_guard的区别。 总结:unique_lock使用起来要比lock_guard更灵活,但是效率会第一点,内存的…...

初探 VS Code + Webview

本文作者为 360 奇舞团前端开发工程师 介绍 VSCode 是一个非常强大的代码编辑器,而它的插件也非常丰富。在开发中,我们经常需要自己编写一些插件来提高开发效率。本文将介绍如何开发一个 VSCode 插件,并在其中使用 Webview 技术。首先介绍一下…...

Codeforces Round 864 (Div. 2)(A~D)

A. Li Hua and Maze 给出两个不相邻的点,最少需要堵上几个方格,才能使得两个方格之间不能互相到达。 思路:显然,对于不邻任何边界的方格来说,最少需要的是4,即上下左右都堵上;邻一个边界就-1&a…...

第3章-运行时数据区

此章把运行时数据区里比较少的地方讲一下。虚拟机栈,堆,方法区这些地方后续再讲。 转载https://gitee.com/youthlql/JavaYouth/tree/main/docs/JVM。 运行时数据区概述及线程 前言 本节主要讲的是运行时数据区,也就是下图这部分&#xff0c…...

delta.io 参数 spark.databricks.delta.replaceWhere.constraintCheck.enabled

总结 默认值true 你写入的df分区字段必须全部符合覆盖条件 .option("replaceWhere", "c2 == 2") false: df1 overwrite tb1: df1中每个分区的处理逻辑: - tb1中存在(且谓词中匹配)的分区,则覆盖 - tb1中存在(谓词中不匹配)的分区,则append - tb1中不存…...

Redis知识点

1. Redis-常用数据结构 Redis提供了一些数据结构供我们往Redis中存取数据,最常用的的有5种,字符串(String)、哈希(Hash)、列表(list)、集合(set)、有序集合(zset&#xf…...

经典数据结构之2-3树

2-3树定义 2-3树,是最简单的B-树,其中2、3主要体现在每个非叶子节点都有2个或3个子节点,B-树即是平衡树,平衡树是为了解决不平衡树查询效率问题,常见的二叉平衡书有AVL树,它虽然提高了查询效率&#xff0c…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...

OpenLayers 分屏对比(地图联动)

注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...