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

volatile

什么是volatile

volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

好,开始讲大家看不懂的东西了!
volatile有三大特性

  1. 保证可见性
  2. 不保证原子性
  3. 有序性

傻了吧,这都是些什么东西啊?别着急,我们一个一个来。

在学习volatile之前,我们先了解一下JMM。什么又是JMM?我只知道JVM。这他妈是啥东西啊?

JMM:java内存模型。jmm是一种抽象的概念,并不真实存在,它描述的是一种规范,通过这种规范定义了程序中的各个变量的访问形式。(仔细读,还是能读懂的)

JMM关于同步的规定(仔细读):

  1. 线程解锁前,必须把共享变量的值刷新回主内存
  2. 线程加锁前,必须读取主内存的最新值到自己的工作内存
  3. 加锁解锁是同一把锁

知道看不懂,开始白话文解释!

在这里插入图片描述

JVM我们的java虚拟机运行程序的时候,是以线程为最小刻度的。而每个线程创建的时候,jvm就会为这个线程创建一个工作内存,该工作内存是私有的,只能被当前线程所访问。
而JMM内存模型中规定:所有的变量都储存在主内存中,所有线程都能访问,但线程对变量的任何操作(读取赋值等)都必须在工作内存中进行,首先要将主内存中的变量拷贝到自己的工作内存中,然后才能对变量进行操作,操作完成后再将变量写回主内存中。

这里我们发现了一个问题

先试想这样一个场景:现在有一个商品只剩下最后一个,如果两个线程同时进来抢,拿到了一个变量:int a = 1;(商品的数量) 这时候这个int a = 1;会拷贝出两份,分别存在于线程1的工作内存和线程2的工作内存。 我们知道,不同线程间是无法访问对方的工作内存的。

这个时候线程1 跑得快一点抢到了最后一个商品,把int a 的值减去1了,然后通知快递部门上门来取货,把这最后一个商品拿走发货,然后把最新的a的值返回给主内存。现在主内存int a 的值等于0。

但对于线程2来说,它现在只看自己的工作内存,不看主内存,对于线程2来说,int a 的值现在还是1。所以它就觉得它也抢到了商品,其实这时主内存中的int a已经是0了,已经没有商品了。这时线程2把自己工作内存的int a 的值减去1,然后通知快递部门来取货,快递来了发现你他妈的商品都卖完了我来取个啥?

上面就出现了超卖的情况,其根本原因就是:多个线程之间不能知道对方的对共享变量的执行情况,大家都是盯着自己的东西在做事。就像两个施工队在山的两边一起往中间打隧道,互相不知道对方的情况,最后两个隧道在山的中间完美错过。

好!那么有没有一个办法,只要有一个线程修改了主内存的变量的值以后,其他的线程能马上知道并获取到最新的值呢?

volatile的可见性

一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量的这种修改(变化)。

先看看没有使用volatile关键字的情况:

1.编写一个类,模拟售卖商品的过程,商品数量我们初始化为 Int a = 1;

class Shop{int a = 1;public void saleOne(){this.a = a-1;}
}

2.测试类

    public static void main(String[] args) {Shop shop = new Shop();new Thread(()->{System.out.println("线程A初始化");try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}shop.saleOne();System.out.println("线程A购买商品完成,剩余商品量:"+shop.a);},"线程A").start();while (shop.a == 1){}System.out.println("主线程,剩余商品量:"+shop.a);}

这里有两个线程,线程A和主线程。 程序启动的时候:

  1. 线程A和主线程分别把shop 对象拷贝一份到自己的工作内存,对于这两个线程来说,shop 对象里的int a 属性的值都是1;
  2. 启动线程A
  3. 线程A等待3秒,再此3秒期间,主线程已经运行到while (shop.a == 1)这行代码,因为判断为true所以一直阻塞。
  4. 3秒过后,线程A调用方法,把自己工作内存中的int a 减 1,并把最新的int a = 0发送到主内存。
  5. 但我们发现,主线程还是一直处于阻塞的状态,对于主线程来说,它不知道int a 的值已经变为0,对主线程来说现在int a 的值还是自己工作内存中的1,所以 while (shop.a == 1)的判断永远为True。不会执行最后一行代码System.out.println(“主线程,剩余商品量:”+shop.a);

在这里插入图片描述

我们加上volatile关键字

class Shop{volatile int a = 1;public void saleOne(){this.a =a-1;}
}

测试代码不变

结果:

在这里插入图片描述

  1. 线程A和主线程分别把shop 对象拷贝一份到自己的工作内存,对于这两个线程来说,shop 对象里的int a 属性的值都是1;
  2. 启动线程A
  3. 线程A等待3秒,再此3秒期间,主线程已经运行到while (shop.a == 1)这行代码,因为判断为true所以一直阻塞。
  4. 3秒过后,线程A调用方法,把自己工作内存中的int a 减 1,并把最新的int a = 0发送到主内存。
  5. 由于volatile的可见性,此时对于主内存来说 int a的值已经由1变为了0, while (shop.a == 1)判断为False。程序就继续往下走,打印出了最新的int a的值:0。这时候商品数量为0之后,我们就不会再出现超卖的情况了

volatile的原子性(不保证)

原子性什么意思呢?

一个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态。

大白话翻译:同一个方法,在一个线程没有执行完之前,其他线程必须给我等着。等我执行完了再放第二个线程进来。以免线程1的操作被线程2给覆盖了。比如synchronized,就保证了原子性。

给我们的Shop类创建一个增加商品库存的方法(每调一次addGoods方法,Int a就+1):

class Shop{volatile int a = 1;public void addGoods(){a++;}public void saleOne(){this.a =a-1;}}

此时int a商品数量是加了volatile 修饰的,保证了不同线程之间的可见性!

测试:

    public static void main(String[] args) {Shop shop = new Shop();for(int i = 0; i < 20;i++){new Thread(()->{shop.addGoods();}).start();}//保证所有20个线程都跑完,只剩下2个线程(主线程和GC线程)的时候代码才继续往下走//其中 Thread.yield() 方法表示主线程不执行,让给其他线程执行while (Thread.activeCount() >2){Thread.yield();}System.out.println("如果保证了原子性,应该的结果是本来的1+20 = 21,但实际的值:"+shop.a);}
  1. 开20个线程去执行addGoods()方法
  2. 最后主线程把int a 的数值打印出来

结果让我们大失所望,每次执行程序得到的结果都不一样

在这里插入图片描述

这里我们知道,volatile不能保证程序的原子性。那为什么呢?

首先明确一点 a++操作不是原子性,它有三步:

  1. 获取主内存的当前值到自己的工作内存
  2. 进行+1操作
  3. 把最新值写回到主内存

尚且a++都不是原子操作,那我们平时的业务代码是不是更长,花的时间也更多?被其他线程覆盖的机会是不是也更大?

好,现在我们来看看上面的20个线程的例子怎么来分析!

  1. 加入现在有l两个线程几乎同时进入到addGoods()方法里面。
  2. 对于A,B两个线程而言,现在int a 的值都拷贝到各自的工作内存中,值都=1。
  3. 现在线程A开始执行a++操作,底层获取到当前值,然后+1,得到值为2,准备把最新值写入到主内存
  4. 这个时候由于多线程的机制,线程A在写入主内存之前被挂起了!
  5. 线程B开始执行了,成功的把int a 从加到2,写入主内存,现在主内存的值是2
  6. 线程A现在又被唤起,完成第3步没有完成的操作,把线程A自己工作内存中的2写入到主内存。
  7. 但现在主内存本来就是2,线程A由于在执行底层的++操作,没有机会去读取到最新的值。

以上!就是整个代码运行流程,解释了volatile为什么不能保证原子性。我知道很多同学还是没看懂,别急,我是红色文章最后会有更直观的例子(单例模式中的线程安全问题),一看就明白了

现在我们想一想,怎么解决volatile这个缺点呢?怎么实现原子性?

  • 1,在addGoods方法加同步锁synchronized
  • 2, AtomicInteger原子类

我们讲第二种:

修改我们的Shop类

class Shop{AtomicInteger atomicInteger = new AtomicInteger(1);public void addGoodsByAtomic(){atomicInteger.getAndIncrement();}
  1. 初始化原子类值为1
  2. 创建新方法,方法体让原子类自增1,整个过程是原子性的。

测试:

 public static void main(String[] args) {Shop shop = new Shop();for(int i = 0; i < 20;i++){new Thread(()->{shop.addGoodsByAtomic();}).start();}while (Thread.activeCount() >2){Thread.yield();}System.out.println("如果保证了原子性,应该的结果是本来的1+20 = 21,但实际的值:"+shop.atomicInteger);}

结果正确:

在这里插入图片描述

为什么原子类保证了原子性?这个设涉及到CAS锁。

volatile的有序性(禁止指令重排)(了解)

我们写的java代码,为了提高性能,在编译器和处理器中往往会进行指令重排,例如我写的某一行代码在23行,当经过编译过后这行代码在150行。

多线程环境中,由于编译器重排的原因,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

简单来说volatile避免了指令重排,也就避免了多线程中可能产生的问题。

volatile的运用场景(重点)

单例模式:

public class type3 {private static type3 type;private type3(){}private static type3 getInstance(){if(type == null){type = new type3();}return  type;}}
public class type4 {private static type4 type;private type4(){}private static synchronized type4 getInstance(){if(type == null){type = new type4();}return  type;}}

但synchronized把整个方法都锁了,在高并发的情况下,太重了。并发性下降了,吞吐量下降了。

所以出现了效率最高,也安全的单例模式写法:双重检查!

public class type5 {private static type5 type;private type5(){}private static type5 getInstance(){if(type == null){synchronized(type5.class){if(type == null){type = new type5();}}}return  type;}}

大家觉得上面的代码有没有什么问题?
我来梳理一下。

  1. 现在有A B两个线程同时进来,都通过了第一次检查。现在到达了synchronized同步锁外面
  2. A线程运气好,被先放进去了,再次检查发现type确实为null,好,放行
  3. A线程new了一个实例出来,这是把这个最新的实例返回给主内存,主内存的对象变量从Null变为有值
  4. A线程完成,B线程被放synchronized开始进行B线程的第二次检查
  5. 但由于type5 变量没有volatile修饰,所以线程B不能马上获取到最新的值,它不知道现在对象已经被new出来了,在线程B自己的工作内存了对象依然为null。
  6. B线程通过第二次检查,又new了一个对象出来。单例的目标没有达成,上面的代码失败。

所以我们要给变量加上volatile关键字:

private static volatile type5 type;

查发现type确实为null,好,放行
3. A线程new了一个实例出来,这是把这个最新的实例返回给主内存,主内存的对象变量从Null变为有值
4. A线程完成,B线程被放synchronized开始进行B线程的第二次检查
5. 但由于type5 变量没有volatile修饰,所以线程B不能马上获取到最新的值,它不知道现在对象已经被new出来了,在线程B自己的工作内存了对象依然为null。
6. B线程通过第二次检查,又new了一个对象出来。单例的目标没有达成,上面的代码失败。

所以我们要给变量加上volatile关键字:

private static volatile type5 type;

相关文章:

volatile

什么是volatile volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制&#xff1a;同步块&#xff08;或方法&#xff09;和 volatile 变量&#xff0c;相比于synchronized&#xff08;synchronized通常称为重量级锁&#xff09;&#xff0c;volatile更…...

JAVA:实现Excel和PDF上下标

1、简介 最近项目需要实现26个小写字母的上下标功能,自己去网上找了所有Unicode的上下标形式,缺少一些关键字母,顾后面考虑自己创建上下标字体样式,以此来记录。 2、Excel Excel本身是支持上下标,我们可以通过Excel单元格的样式来设置当前字体上下标,因使用的是POI的m…...

AI写稿软件,最新的AI写稿软件有哪些

写作已经成为各行各业无法绕开的重要环节。不论是企业的广告宣传、新闻媒体的报道、还是个人自媒体的内容创作&#xff0c;文字都扮演着不可或缺的角色。随着信息的爆炸式增长&#xff0c;写作的需求也不断攀升&#xff0c;这使得许多人感到困扰。时间不够用、创意枯竭、写作技…...

干货:数据仓库基础知识(全)

1、什么是数据仓库&#xff1f; 权威定义&#xff1a;数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合&#xff0c;用于支持管理决策。 1&#xff09;数据仓库是用于支持决策、面向分析型数据处理&#xff1b; 2&#xff09;对多个异构的数据源有效集…...

二分搜索简介

概念&#xff1a; 二分搜索算法&#xff08;Binary Search&#xff09;是一种高效的搜索算法&#xff0c;用于在有序数组中查找特定元素的位置。它的基本思想是将数组分为两部分&#xff0c;通过比较目标值与数组中间元素的大小关系&#xff0c;确定目标值可能存在的区间&…...

虚拟车衣VR云展厅平台扩大了展览的触达范围

传统展厅主要是以静态陈列的形式来传达内容&#xff0c;主要的展示形式有图片、视频等&#xff0c;具有一定的局限性&#xff0c;体验感较差&#xff0c;客户往往不能深入地了解信息和细节内容。 VR全景看车是通过虚拟现实技术实现逼真的汽车观赏和试乘体验。消费者可以通过智能…...

云部署家里的服务器

1.固定静态ip 查看ip地址&#xff0c;en开头的 ifconfig查看路由器ip&#xff0c;via开头的 ip route修改配置文件 cd /etc/netplan/ #来到这个文件夹 sudo cp 01-network-manager-all.yaml 01-network-manager-all.yaml.bak #先备…...

【利用冒泡排序的思想模拟实现qsort函数】

1.qsort函数 1.1qsort函数的介绍 资源来源于cplusplus网站 1.2qsort函数的主要功能 对数组的元素进行排序 对数组中由 指向的元素进行排序&#xff0c;每个元素字节长&#xff0c;使用该函数确定顺序。 此函数使用的排序算法通过调用指定的函数来比较元素对&#xff0c;并将指…...

[plugin:vite:css] [sass] Undefined mixin.

前言&#xff1a; vite vue3 TypeScript环境 scss报错&#xff1a; [plugin:vite:css] [sass] Undefined mixin. 解决方案&#xff1a; 在vite.config.ts文件添加配置 css: {preprocessorOptions: {// 导入scss预编译程序scss: {additionalData: use "/resources/_ha…...

【论文阅读】大语言模型中的文化道德规范知识

摘要&#xff1a; 在已有的研究中&#xff0c;我们知道英语语言模型中包含了类人的道德偏见&#xff0c;但从未有研究去检测语言模型对不同国家文化的道德差异。 我们分析了语言模型包含不同国家文化道德规范的程度&#xff0c;主要针对两个方面&#xff0c;其一是看语言模型…...

51单片机实训项目之产品数量计数器

/********************************************************************************* * 【实验平台】&#xff1a; QX-MCS51 单片机开发板 * 【外部晶振】&#xff1a; 11.0592mhz * 【主控芯片】&#xff1a; STC89C52 * 【编译环境】&#xff1a; Keil μVisio3 * 【程序…...

Scala第七章节

Scala第七章节 scala总目录 章节目标 掌握继承和抽象类相关知识点掌握匿名内部类的用法了解类型转换的内容掌握动物类案例 1. 继承 1.1 概述 实际开发中, 我们发现好多类中的内容是相似的(例如: 相似的属性和行为), 每次写很麻烦. 于是我们可以把这些相似的内容提取出来单…...

C语言进程的相关操作

C语言进程的相关操作 进程简介 每个进程都有一个非负整数形式到的唯一编号&#xff0c;即PID&#xff08;Process Identification&#xff0c;进程标识&#xff09;PID在任何时刻都是唯一的&#xff0c;但是可以重用&#xff0c;当进程终止并被回收以后&#xff0c;其PID就可…...

数据结构学习系列之链式栈

链式栈&#xff1a;即&#xff1a;栈的链式存储结构&#xff1b;分析&#xff1a;为了提高程序的运算效率&#xff0c;应采用头插法和头删法&#xff1b;进栈&#xff1a; int push_link_stack(stack_t *link_stack,int data) {if(NULL link_stack){printf("入参合理性检…...

too many session files in /var/tmp

Linux中Too many open files 问题分析和解决_e929: too many viminfo temp files-CSDN博客...

【7.0】打开未知来源安装应用

默认打开未知来源安装应用 frameworks\base\packages\SettingsProvider\res\values\defaults.xml <bool name"def_install_non_market_apps">false</bool>...

安装ipfs-swarm-key-gen

安装ipfs-swarm-key-gen Linux安装go解释器安装ipfs-swarm-key-gen Linux安装go解释器 https://blog.csdn.net/omaidb/article/details/133180749 安装ipfs-swarm-key-gen # 编译ipfs-swarm-key-gen二进制文件 go get -u github.com/Kubuxu/go-ipfs-swarm-key-gen/ipfs-swarm…...

BASH shell脚本篇5——文件处理

这篇文章介绍下BASH shell中的文件处理。之前有介绍过shell的其它命令&#xff0c;请参考&#xff1a; BASH shell脚本篇1——基本命令 BASH shell脚本篇2——条件命令 BASH shell脚本篇3——字符串处理 BASH shell脚本篇4——函数 在Bash Shell脚本中&#xff0c;可以使用…...

ElementUI之首页导航及左侧菜单(模拟实现)

目录 ​编辑 前言 一、mockjs简介 1. 什么是mockjs 2. mockjs的用途 3. 运用mockjs的优势 二、安装与配置mockjs 1. 安装mockjs 2. 引入mockjs 2.1 dev.env.js 2.2 prod.env.js 2.3 main.js 三、mockjs的使用 1. 将资源中的mock文件夹复制到src目录下 2. 点击登…...

Java开源工具库使用之Lombok

文章目录 前言一、常用注解1.1 AllArgsConstructor/NoArgsConstructor/RequiredArgsConstructor1.2 Builder1.3 Data1.4 EqualsAndHashCode1.5 Getter/Setter1.6 Slf4j/Log4j/Log4j2/Log1.7 ToString 二、踩坑2.1 Getter/Setter 方法名不一样2.2 Builder 不会生成无参构造方法2…...

2026年AI智能体大爆发:下一个十年风口,普通人的超级财富密码

比尔盖茨曾断言&#xff1a;“AI智能体&#xff08;AI Agent&#xff09;将彻底改变人们使用计算机的方式。”如果说2023年是大语言模型&#xff08;LLM&#xff09;的启蒙元年&#xff0c;那么到2026年&#xff0c;具备“感知-规划-行动”自主闭环能力的AI智能体将迎来真正的商…...

基于主从博弈的主动配电网阻塞管理探索

基于主从博弈的主动配电网阻塞管理 首先&#xff0c;在日前市场中&#xff0c;LA&#xff08;负荷聚合商&#xff09;根据历史数据预测次日向上级电网购电的电价信息和预测分布式电源(燃气轮机)出力、风电场出力信息&#xff0c;同时考虑事前与用户签订协议的可中断负荷&#x…...

VSCode党必看!用轻量级方案玩转LaTeX:2024年TexLive+VSCode配置全攻略

VSCode党必看&#xff01;用轻量级方案玩转LaTeX&#xff1a;2024年TexLiveVSCode配置全攻略 对于习惯在VSCode中高效编码的开发者而言&#xff0c;切换到传统LaTeX编辑器往往意味着要放弃熟悉的快捷键、扩展生态和流畅的代码体验。本文将带你用完全基于VSCode的轻量级方案构建…...

JPEXS Free Flash Decompiler与Web3.0存储:去中心化SWF文件管理的终极指南

JPEXS Free Flash Decompiler与Web3.0存储&#xff1a;去中心化SWF文件管理的终极指南 【免费下载链接】jpexs-decompiler JPEXS Free Flash Decompiler 项目地址: https://gitcode.com/gh_mirrors/jp/jpexs-decompiler JPEXS Free Flash Decompiler是一款功能强大的开源…...

Verilog进阶实战:独热码状态机设计序列检测器的核心技巧

1. 独热码状态机的设计哲学 第一次接触独热码(One-Hot)编码时&#xff0c;我盯着那串只有一个1的状态编码看了半天——这不就是硬件版的"单选题"吗&#xff1f;每个状态都有自己的专属VIP通道&#xff0c;这种设计理念在中小规模状态机中简直是降维打击。记得去年做电…...

零成本实现3D模型跨平台迁移:Blender到Unreal Engine的无缝解决方案

零成本实现3D模型跨平台迁移&#xff1a;Blender到Unreal Engine的无缝解决方案 【免费下载链接】bl_datasmith Blender addon to export UE4 Datasmith format 项目地址: https://gitcode.com/gh_mirrors/bl/bl_datasmith 你是否曾遇到这样的困境&#xff1a;在Blender…...

零基础图解VLN视觉语言导航:从输入到决策的完整模型拆解

1. 视觉语言导航&#xff08;VLN&#xff09;是什么&#xff1f; 想象你第一次去朋友家做客&#xff0c;对方在电话里说&#xff1a;“进门左转&#xff0c;看到红色沙发后直走&#xff0c;右手边第二个房间就是。”这时候你的大脑会做三件事&#xff1a;用眼睛观察环境&#x…...

智能客服系统搭建实战:基于NLP与微服务架构的AI客服实现指南

最近在帮公司搭建一套智能客服系统&#xff0c;从零开始踩了不少坑&#xff0c;也积累了一些实战经验。今天就来聊聊&#xff0c;如何基于当前比较成熟的 NLP 和微服务架构&#xff0c;一步步构建一个能扛住真实业务压力的 AI 客服系统。整个过程涉及技术选型、核心模块实现、性…...

别再傻傻线性扫描了!用Python+Scikit-learn手把手实现IVFFlat图像相似度搜索

用PythonScikit-learn实现IVFFlat图像搜索引擎&#xff1a;从原理到实战 当你面对十万张未分类的图片库&#xff0c;如何快速找到与目标图片最相似的十张&#xff1f;传统线性扫描需要计算所有图片特征的距离&#xff0c;耗时呈线性增长。本文将手把手教你用IVFFlat算法构建高效…...

OpenClaw飞书机器人配置指南:百川2-13B-4bits量化模型对话触发

OpenClaw飞书机器人配置指南&#xff1a;百川2-13B-4bits量化模型对话触发 1. 为什么选择OpenClaw飞书百川2的组合&#xff1f; 去年我接手了一个小团队的日报自动化项目&#xff0c;需要每天收集5个成员的进度更新并生成汇总报告。最初尝试用Python脚本钉钉机器人&#xff0…...