JUC并发编程高级篇第二章之Volatile(解决数据可见性和可续性)

news/2024/4/20 14:38:12/文章来源:https://blog.csdn.net/qq_42292373/article/details/129992504

文章目录

  • 1、Volatile简介
  • 2、Volatile的特性
    • 2.1、 可见性(重点)
      • 2.1.1、没有可见性的代码案例
      • 2.1.2、可见性读取规则
    • 2.2、 禁止指令重排序
      • 2.2.1、 不存在依赖,代码顺序就可以随便重排吗?
    • 2.3、 不保证原子性
        • 2.3.1、 不保证原子性代码案例
        • 2.3.2、 不保证原子性原因
        • 2.3.3、 如何能保证原子性
  • 3、Volatile的内存语义(最核心的一句话)
  • 4、Volatile的原理-内存屏障
    • 4.1、 内存屏障概述
    • 4.2、 类型
    • 4.3、 屏障的插入策略
  • 5、 volatile的正确使用姿势
    • 5.1 DCL双端检索
      • 5.1.1、多线程环境下单例模式出现的问题
      • 5.1.2、双端检索机制解决办法
      • 5.1.3、双端检索机制的隐患
      • 5.1.4、解决双端检索机制的隐患

1、Volatile简介

Volatile是Java中的一个关键字,用于修饰变量。当一个变量被声明为volatile时,它的值可能会被多个线程同时访问和修改。

2、Volatile的特性

2.1、 可见性(重点)

可见性 : 当一个线程修改了volatile变量的值,其他线程可以立即看到这个变量的最新值。

2.1.1、没有可见性的代码案例

public class interruputDemo1 {static boolean flag = false;public static void main(String[] args) throws InterruptedException {new Thread(() -> {System.out.println("我开始运行了");while (true) {if (flag) {System.out.println("我检测到编程true");break;}}}, "t1").start();Thread.sleep(1000);flag = true;System.out.println("主线程执行完毕");}
}

分析

  • 主线程修改了flag之后没有立马将其刷新到主内存,所以t1线程看不到
  • 主线程修改了flag之后没有立马将其刷新到主内存,所以t1线程看不到

2.1.2、可见性读取规则

在这里插入图片描述
总结

  • 当第一个操作为volatle读时,不论第二个操作是什么,都不能重排序。这个操作保证了volatile读之后的操作不会被重排到volatile读之前。
  • 当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序。这个操作保证了volatile写之前的操作不会被重排到volatle写之后
  • 当第一个操作为volatile写时,第二个操作为volatile读时,不能重排。

2.2、 禁止指令重排序

  • 重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序
  • 不存在数据依赖关系,可以重排序 (有例外,看 1.1.2.1章节);
  • 存在数据依赖关系,禁止重排序
  • 但重排后的指令绝对不能改变原有的串行语义!这点在并发设计中必须要重点考虑

2.2.1、 不存在依赖,代码顺序就可以随便重排吗?

public class VolatileTest {int i = 0;volatile boolean flag = false;public void write() {i = 2;flag = true;}public void read() {if (flag) {System.out.println("------" + i);}}
}

解析:

在下面的代码中虽然i=2 和 flag=true 没有严格的依赖关系, 但是如果2者的顺序发生了改变,先执行flag=true , 此时另一个线程突然访问read(),flag=true生效,但是i=2还没有赋值完成,就会导致数据错误, 此时就需要给flag加入volatile禁止指令重排

2.3、 不保证原子性

对于复合操作,例如i++,volatile变量并不能保证原子性,因为i++操作实际上是由三个操作组成的:读取i的值、对i的值进行加1操作、将结果写回i。如果多个线程同时执行i++操作,会出现竞争条件,需要使用同步机制来保证原子性

2.3.1、 不保证原子性代码案例

class MyNumber{volatile  int number;public  void add(){number++;}public int getNumber() {return number;}
}public class InterruputDemo1 {public static void main(String[] args) throws InterruptedException {MyNumber myNumber = new MyNumber();for (int i = 0; i < 10; i++) {new Thread(()->{for (int j = 0; j < 1000; j++) {myNumber.add();}},String.valueOf(i)).start();}Thread.sleep(2000);System.out.println(myNumber.getNumber());}
}输出 9556

2.3.2、 不保证原子性原因

  • 假如A,B两个线程同时从主内存中获取值为1 , 此时两个线程分别进行+1的操作;
  • 这个时候A线程先完成了+1的操作2,把2写道了主内存,
  • 但是因此加了volatile,B线程感知到主线程的内容发生了修改了;
  • 所以B线程会中断这次的+1的操作,重新进行获取值,相当于这些写的操作直接丢失了

2.3.3、 如何能保证原子性

参考下一章节CAS

3、Volatile的内存语义(最核心的一句话)

  • 当写一个volatile变量的时候,JMM会把线程对应本地内存中共享变量值 立即刷新回主内存
  • 当读volatitle的时候,JMM会把该线程对应的本地内存值设置为无效,重新回到主内存中读取最新的共享变量

4、Volatile的原理-内存屏障

4.1、 内存屏障概述

volatile 的底层实现原理是内存屏障,也称内存栅栏,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作,避免代码重排序

4.2、 类型

类型指令示例
写屏障(Store Memory Barrier)对 volatile 变量的写指令后会加入写屏障;在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中 在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中
读屏障(Load Memory Barrier)对 volatile 变量的读指令前会加入读屏障;在读指令之前插入读屏障,让工作肉存或CPU高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据

细分

类型指令示例指令说明
LoadLoadLoad1;LoadLoad;Load2保证Load1的读取操作在load2及后续读取操作之前执行
StoreStoreStore1;StoreStore;Store2在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
LoadStoreStore1;StoreStore;Store2在store2及其后的写操作执行前,保证了load1的读操作已经读取结束
StoreLoadStore1;StoreLoad;Load2保证了Store1的写操作已经刷新到了主内存之后,load2及其后的读操作才能执行

4.3、 屏障的插入策略

volatile读插入内存屏障生成的指令序列示意图
在这里插入图片描述

  • 在每个volatile读操作的后面插入一个LoadLoad屏障 ;
    禁止处理器把上面的volatile读与下面的普通读重排序。
  • 在每个volatile读操作的后面插入一个LoadStore屏障 ;
    禁止处理器把上面的volatile读与下面的普通写重排序。

volatile写插入内存屏障生成的指令序列示意图
在这里插入图片描述

  • 在每个volatie写操作的前面插入一个 StoreStore屏障;
    可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中。
  • 在每个volatile 写操作的后面插入一个 StoreLoad屏障
    作用是避免volatile写与后面可能有的volatile读/写操作重排序

5、 volatile的正确使用姿势

  • 单一赋值可以,but含复合运算赋值不可以(i++之类)
  • 状态标志,判断业务是否结束
  • 开销较低的读,写锁策略
  • DCL双端锁的发布

5.1 DCL双端检索

5.1.1、多线程环境下单例模式出现的问题

package com.bwie.demo;import java.time.temporal.ValueRange;
import java.util.concurrent.atomic.AtomicInteger;/*** 不能保证原子性*/
public class SingletonDemo  {public static SingletonDemo instance = null;public SingletonDemo() {System.out.println("我是构造方法");}private  static SingletonDemo getInstance(){if (instance==null){instance = new SingletonDemo ();}return instance;}public static void main(String[] args) {SingletonDemo instance = SingletonDemo.getInstance();SingletonDemo instance1 = SingletonDemo.getInstance();System.out.println(instance.equals(instance1));}
}

通过上面的案例,我们可以知道因为是单例模式,所以构造方法只会输出一次,但是多线程的环境下,则不然啦
多线程的环境下

public class SingletonDemo {public static SingletonDemo instance = null;private SingletonDemo() {System.out.println("我是构造方法");}private static  SingletonDemo getInstance() {if (instance == null) {instance = new SingletonDemo();}return instance;}public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executorService.submit(() -> {instance =  SingletonDemo.getInstance();});}executorService.shutdown();}
}

输出结果

我是构造方法
我是构造方法
我是构造方法

5.1.2、双端检索机制解决办法

双端检索机制

public class SingletonDemo {public static SingletonDemo instance = null;private SingletonDemo() {System.out.println("我是构造方法");}private static  SingletonDemo getInstance() {if (instance == null) {synchronized (SingletonDemo.class) {if (instance==null){instance = new SingletonDemo();}}}return instance;}public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executorService.submit(() -> {instance =  SingletonDemo.getInstance();});}executorService.shutdown();}
}

5.1.3、双端检索机制的隐患

从上面的结果我们可以看出来,好像加了双端检索机制貌似就没有出现问题啦, 而且运行检验的时候,他的确是只输出了一次构造方法吗?
但是问题真的就这么解决了吗?

DCL(双端检索机制)机制不一定安全,因为有指令重排的存在,加入voliate则可以禁止指令重排
原因是在某个线程执行到第一次检测,读取到instance为null的时候,instance对象没有完成对象的初始化,而
instance = new SingletonDemo();又可以分为三步
memory = allocate() 分配内存空间 语句1
instance(memory ) 初始化对象 语句2
instance = memory 实例指向内空间 语句3

因为语句2和语句3没有数据依赖性,所以 他们的顺序可以指令重排, 如果为132顺序的话.在对象还没有完成对象的初始化的时候,直接把null的对象指向内存空间,会导致出现null的结果

5.1.4、解决双端检索机制的隐患

加上voliate

public class SingletonDemo {public static volatile SingletonDemo instance = null;private SingletonDemo() {System.out.println("我是构造方法");}private static  SingletonDemo getInstance() {if (instance == null) {synchronized (SingletonDemo.class) {if (instance==null){instance = new SingletonDemo();}}}return instance;}public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executorService.submit(() -> {instance =  SingletonDemo.getInstance();});}executorService.shutdown();}
}

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

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

相关文章

Serverless MQTT 服务即将正式上线、新增 2 个平台安装包

3 月&#xff0c;EMQX 开源版发布了 v5.0.19、v5.0.20 以及 v5.0.21 三个版本&#xff0c;提供 Rocky Linux 9 以及 MacOS 12 Intel 平台安装包。企业版发布了 v4.4.15 以及 v4.4.16 版本&#xff0c;提供了 Apache IoTDB 支持、HStreamDB 最新版本的适配、MongoDB 6.0 支持等多…

(链表)判断链表中是否有环(快慢指针法)

文章目录前言&#xff1a;问题描述&#xff1a;解题思路&#xff1a;代码实现&#xff1a;总结&#xff1a;前言&#xff1a; 此篇是针对链表的经典练习题。 问题描述&#xff1a; 判断给定的链表中是否有环。如果有环则返回true&#xff0c;否则返回false。 数据范围&#…

【计算机组成原理笔记】

【计算机组成原理笔记】 1.1 计算机系统简介 计算机系统由软件和硬件组成。软件又可分为系统软件和应用软件。 计算机体系结构指的是&#xff08;机器语言&#xff09;程序员所看到的计算机系统属性概念性的结构与功能特性。&#xff08;研究有无乘法指令&#xff09; 计算机…

3036: 莫比乌斯最大值isUsefulAlgorithm(2023郑州轻工业大学校赛

题意&#xff1a; 有n个问题和闲聊 问题的格式是’what’s S问题S_{问题}S问题​’ 闲聊的格式是 S问题S_{问题}S问题​S回答S_{回答}S回答​&#xff0c;S问题S_{问题}S问题​的长度>0 对于每个 S回答S_{回答}S回答​ &#xff0c;只能回答在这句话之前提问的问题 那么…

线程池ThreadPoolExecutor原理

文章目录线程池ThreadPoolExecutor原理核心参数如何设置核心线程数和最大线程数线程空闲时间阻塞队列设置线程池的五种状态原理执行流程拒绝策略线程淘汰机制线程池ThreadPoolExecutor原理 核心参数如何设置 核心线程数和最大线程数 线程池中线程数量我们一般要区分任务的类…

操作技巧 | Revit中如何新建系统类型并赋予颜色?

大家好&#xff0c;这里是行走的安利机---建模助手。 新建系统后&#xff0c;把材质赋予系统&#xff0c;以做出不同颜色的管道和风管系统&#xff0c;那么&#xff1a;Revit中如何新建系统类型并赋予颜色呢&#xff1f; 下面小编说下解决方案。 REVIT 具体解决办法如下 正…

携多款产品亮相“深圳先进制造业集群展”,华秋积极探索发展机遇

4月7日&#xff0c;在深圳市工业和信息化局指导下&#xff0c;由深圳先进技术研究院作为总促进机构的深圳市新一代信息通信产业集群于第十一届中国电子信息博览会&#xff08;CITE2023&#xff09;期间举办 “深圳先进制造业集群展”。 本次先进制造业集群展以“科技带动产业创…

设计干货:PCB为什么要拼版?PCB拼版的适用方式分享

PCB为什么要拼版&#xff1f; 拼版主要是为了满足 生产的需求 &#xff0c;有些PCB板太小&#xff0c;不满足做夹具的要求&#xff0c;所以需要拼在一起进行生产。 拼版也可以提高SMT贴片的 焊接效率 &#xff0c;如只需要过一次SMT&#xff0c;即可完成多块PCB的焊接。 同时…

FPGA纯verilog实现UDP通信,三速网自协商仲裁,动态ARP和Ping功能,提供工程源码和技术支持

目录1、前言2、我这里已有的UDP方案3、UDP详细设计方案MAC层发送MAC发送模式ARP发送IP层发送IP发送模式UDP发送MAC层接收ARP接收IP层接收UDP接收SMI读写控制SMI配置10/100/1000M仲裁ICMP应答 (ping)ARP缓存CRC校验以太网测试模块RGMII转GMII模块4、vivado工程详解5、上板调试验…

《大众金融》企业级开发实战

目录 主要内容 1 配置中心简介 1.1 什么是配置 1.2 传统配置形式存在的问题 1.3 配置中心的作用 2 Apollo简介 2.3 Apollo特性 2.4 产品对比 2.5 Apollo初体验 2.5.1 访问控制台 应用配置中心Apollo-讲义 主要内容 1&#xff09;了解配置中心的概念以及使用场景 2&…

单元测试系列 | 如何更好地测试依赖外部接口的方法

背景 在现在这个微服务时代&#xff0c;我们项目中经常都会遇到很多业务逻辑是依赖其他服务或者第三方接口。工作中各位同学对于这类型场景的测试方式也是五花八门&#xff0c;有些是直接构建一个外部mock服务&#xff0c;返回一些固定的response;有些是单元测试都不写&#x…

Linux复习 / 命令与权限部分QA梳理

文章目录前言Q&AshellQ&#xff1a;什么是shell&#xff1f;Q&#xff1a;shell的作用&#xff1f;Q&#xff1a;为什么要有shell&#xff1f;Q&#xff1a;shell的生命周期多长&#xff1f;Q&#xff1a;shell的原理/实现是怎样的&#xff1f;Q&#xff1a;为什么会有内建…

Scrum Master 应该采取哪些措施来提高团队效率?

项目经理应该从这5方面提高团队的开发效率 1、目标明确有时间节点 提高团队开发效率&#xff0c;最重要的是明确目标与期限。制定SMART目标&#xff0c;明确告知成员要实现什么&#xff0c;输出什么&#xff0c;标准以及时限等&#xff0c;需要考虑目标的可达成性和目标与项目的…

【牛客刷题专栏】0x17:JZ17打印从1到最大的n位数(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录前言问题…

Java初阶(异常)

文章目录一、异常的结构体系二、异常的处理2.1 防御式编程2.2 异常的抛出2.4 异常的捕获&#xff08;异常的具体处理方式&#xff09;&#xff08;1&#xff09;异常声明 throws&#xff08;2&#xff09; 捕获处理 try-catch2.4 异常的处理流程三、自定义异常类一、异常的结构…

go学习线路图

1. go学习线路图 1.1.2. 资源 先决条件 GoSQL 通用开发技能 学习 GIT&#xff0c;在 GitHub 上建立一些仓库&#xff0c;与其它人分享你的代码了解 HTTP(S) 协议&#xff0c;request 方法&#xff08;GET, POST, PUT, PATCH, DELETE, OPTIONS&#xff09;不要害怕使用 Google&a…

和数软件荣获上海市“专精特新”企业荣誉认定

近日&#xff0c;上海市经济和信息化委员会公示了2022年上海市“专精特新”企业名单。根据《关于组织开展2022年创新型中小企业评价、专精特新中小企业认定和复核工作的通知》&#xff08;沪经信企〔2022〕776号&#xff09;&#xff0c;经专家评审和综合评估&#xff0c;上海和…

学会吊打面试官之map

小白&#xff1a;大牛&#xff0c;我最近学习了一些C的STL容器&#xff0c;但是我还是有一些疑惑&#xff0c;特别是对于map&#xff0c;我不太理解它的底层实现和具体用法。能否跟我讲一下&#xff1f; 大牛&#xff1a;当然可以啊&#xff0c;map是一种非常常用的关联式容器…

小企业选择什么样的CRM系统比较合适,有什么特点?

CRM客户管理系统已经成为各种规模的企业&#xff0c;特别是小型企业的重要工具。CRM系统帮助小型企业更有效地管理客户数据和互动&#xff0c;简化销售流程&#xff0c;并提高客户满意度。市场上有如此多的选择&#xff0c;小企业该如何选择合适的CRM系统&#xff1f; 什么是C…

深圳CPDA|如何着手商业数据分析?

商业数据分析是一项非常重要的工作&#xff0c;可以帮助企业做出更明智的决策。 下面是一些着手商业数据分析的步骤&#xff1a; 1.确定你的问题 首先需要明确你想要解决什么问题。 这通常需要与业务团队沟通&#xff0c;以便了解他们正在寻找哪些信息。 2.收集数据 收集数…