Volatile

目录

介绍

Volatile保证可见性的原理

可见性问题

原理

Volatile保证有序性的原理

指令重排

内存屏障

如何解决volatile不保证原子性问题?

由Volatile解决的单例模式中双重检索问题(DCL)


介绍

volatile 是 Java 虚拟机提供的轻量级的同步机制(三大特性)

  • 保证可见性

  • 保证有序性(禁止指令重排)

  • 不保证原子性

性能:volatile 修饰的变量进行读操作与普通变量几乎没什么差别,但是写操作相对慢一些,因为需要在本地代码中插入很多内存屏障来保证指令不会发生乱序执行,但是开销比锁要小

synchronized 无法禁止指令重排和处理器优化,为什么可以保证有序性和可见性

  • 加了锁之后,只能有一个线程获得到了锁,获得不到锁的线程就要阻塞,所以同一时间只有一个线程执行,相当于单线程,由于数据依赖性的存在,单线程的指令重排是没有问题的

  • 线程加锁前,将清空工作内存中共享变量的值,使用共享变量时需要从主内存中重新读取最新的值;线程解锁前,必须把共享变量的最新值刷新到主内存中(JMM 内存交互章节有讲)

Volatile保证可见性的原理

可见性问题

可见性问题指的是一个线程在访问一个共享变量的时候,其他线程对该共享变量的修改对于第一个线程来说是不可见的。

在Java中造成可见性问题的原因是Java内存模型(JMM),在Java内存模型中,规定了共享变量是存放在主内存中,然后每个线程都有自己的工作内存,而线程对共享变量的操作,必须先从主内存中读到工作内存中去,至于什么时候写回到主内存是不可预知的,这就导致每个线程之间对共享变量的操作是封闭的,其他线程不可见的。

通过volatile修饰的变量,当一个线程改变了该变量的值,会将共享变量值立即刷新回主内存。而线程读取共享变量必须从主内存中读取,这样就实现了并发下共享资源的可见性。

原理

volatile 修饰的变量,汇编指令中会存在一个lock指令的前缀。会将当前处理器缓存行的数据写回到系统内存,同时触发缓存一致性协议,使在其他CPU里缓存了该内存地址的数据无效。

对声明了volatile关键字的变量进行写操作,JVM会向处理器发送一条lock前缀的指令,将这个变量所在的缓存行立即写回系统内存。并且为了保证各个处理器的缓存是一致的,实现了缓存一致性协议,各个处理通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,那么下次对这个数据进行操作,就会重新从系统内存中获取最新的值。对应JMM来说就是:

  1. Lock前缀的指令让线程工作内存中的值写回主内存中;
  2. 通过缓存一致性协议,其他线程如果工作内存中存了该共享变量的值,就会失效;
  3. 其他线程会重新从主内存中获取最新的值;

Volatile保证有序性的原理

指令重排

指令重排序是指编译器或处理器为了优化程序性能而重新排列指令执行顺序的一种技术,对于一些与执行顺序没有关联的语句,CPU就可能会进行指令重排,在单线程下,这是没有问题的,但是在并发的情况下,就可能会造成指令执行顺序出错而导致的情况

指令重排实例:

  • ex1:

    public void mySort() {int x = 11; //语句1int y = 12; //语句2 x = x + 5;  //语句3y = x * x;  //语句4
    }

    执行顺序可能是:1 2 3 4、2 1 3 4、1 3 2 4

    指令重排也是有限制的,不会出现:4 3 2 1,语句 4 需要依赖于 y 以及 x 的申明,因为存在数据依赖,无法首先执行,在单线程下,不会出现问题

  • ex2:

    int num = 0;
    boolean ready = false;
    // 线程1 执行此方法
    public void actor1(I_Result r) {if(ready) {r.r1 = num + num;} else {r.r1 = 1;}
    }
    // 线程2 执行此方法
    public void actor2(I_Result r) {num = 2;ready = true;
    }

    情况一:线程 1 先执行,ready = false,结果为 r.r1 = 1

    情况二:线程 2 先执行 num = 2,但还没执行 ready = true,线程 1 执行,结果为 r.r1 = 1

    情况三:线程 2 先执行 ready = true,线程 1 执行,进入 if 分支结果为 r.r1 = 4

    情况四:线程 2 执行 ready = true,切换到线程 1,进入 if 分支为 r.r1 = 0,再切回线程 2 执行 num = 2,发生指令重排。

Volatile通过内存屏障的方式解决指令重排序问题

内存屏障

内存屏障分为以下4类:

屏障类型指令示例说明
LoadLoad BarriesLoad1;LoadLoad;Load2确保Load1数据的装载先于Load2以及后续装载指令的装载。
StoreStore BarriesStore1;StoreStore;Store2确保Store1数据刷新到内存先于Store2以及后续存储指令的存储。
LoadStore BarriesLoad1;LoadStore;Store2确保Load1数据的装载先于Store2数据刷新到内存以及后续存储指令的存储。
StoreLoad BarriesStore1;StoreLoad;Load2确保Store1数据刷新到内存先于Load2数据的装载以及后续装载指令的装载。
  • 在每个volatile写操作的前面插入一个StoreStore屏障。
  • 在每个volatile写操作的后面插入一个StoreLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadLoad屏障。
  • 在每个volatile读操作的LoadLoad屏障后面插入一个LoadStore屏障。 

volatile写操作的内存屏障示意图:

volatile读操作的内存屏障示意图:

如何解决volatile不保证原子性问题?

  • 在方法上加入 synchronized,虽然能够保证原子性,但是为了解决number++,而引入重量级的同步机制不太值得。

  • 如何不加synchronized解决number++在多线程下是非线程安全的问题?使用AtomicInteger(前面CAS内容中有介绍)。

由Volatile解决的单例模式中双重检索问题(DCL)

不使用Volatile修饰变量

public class Singleton {private static Singleton instance = null;private Singleton(){ }public static Singleton getInstance() {if(instance == null) {synchronized(Singleton.class) {if(instance == null) {//这里可能会出现指令重排序问题instance = new Singleton();}}}return instance;}}

Instance=new Singleton()可以分为3步

//创建一个变量instance分为3步
//第一步:分配内存空间
memory = allocate();
//第二步:初始化对象
NewInstance(memory);  
//第三步:设置instance指向刚分配的内存地址
instance = memory;     

其中第一步的顺序是不会发生变化的,但是第2、3步功能跟他们的顺序是没有关系的,在单线程下,哪一个先执行都可以

//创建一个变量instance分为3步
//第一步:分配内存空间
memory = allocate();
//第三步:设置instance指向刚分配的内存地址
instance = memory;   
//第二步:初始化对象
NewInstance(memory);  

在是在多线程并发情况下:如果一个线程A由于指令重排,在设置instance指向刚分配的内存地址但还未初始化对象时,注意此时instance已经不为null了,正好另外一个线程B调用该方法,将会获得一个未初始化完毕的单例。

加入Volatile之后的DCL代码:

public class Singleton {//如果不使用volatile,也可以使用静态内部类的方式保证单例性private static volatile Singleton instance = null;private Singleton(){ }public static Singleton getInstance() {if(instance == null) {synchronized(Singleton.class) {if(instance == null) {instance = new Singleton();}}}return instance;}}

静态内部类保证单例性

public class SingletonDemo {private SingletonDemo() { }private static class SingletonDemoHandler {private static SingletonDemo instance = new SingletonDemo();}public static SingletonDemo getInstance() {return SingletonDemoHandler.instance;}
}

因为类加载本身就是懒惰的,在没有调用getInstance方法时是没有执行SingletonDemoHandler内部类的类加载操作的。静态内部类不会随着外部类的加载而加载, 这是静态内部类和静态变量的区别。同时也不会有并发问题,因为是通过类加载创建的单例, 由JVM保证不会出现线程安全。

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

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

相关文章

160.相交链表

题目描述 解题思路 ————看评论区大神的思路———— 设「第一个公共节点」为 node ,「链表 headA」的节点数量为 aaa ,「链表 headB」的节点数量为 bbb ,「两链表的公共尾部」的节点数量为 ccc ,则有: 头节点 …

llm dpo loss 实现 训练实例

原视频地址 dpo loss 过程代码实现感谢: 过程 代码实现 import paddle from tqdm import tqdmclass RefModel(paddle.nn.Layer):def __init__(self, hidden_size, voc_size):super(RefModel, self).__init__()self.em paddle.nn.Embedding(voc_size, hidden_size)…

C++——栈和队列容器

前言:这篇文章我们将栈和队列两个容器放在一起进行分享,因为这两个要分享的知识较少,而且两者在结构上有很多相似之处,比如栈只能在栈顶操作,队列只能在队头和队尾操作。 不同于前边所分享的三种容器,这篇…

CNAS软件测试公司有什么好处?如何选择靠谱的软件测试公司?

CNAS认可是中国合格评定国家认可委员会的英文缩写,由国家认证认可监督管理委员会批准设立并授权的国家认可机构,统一负责对认证机构、实验室和检验机构等相关机构的认可工作。 在软件测试行业,CNAS认可具有重要意义。它标志着一个软件测试公…

最新在线工具箱网站系统源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 系统内置高达72种站长工具、开发工具、娱乐工具等功能。此系统支持本地调用API,同时还自带免费API接口, 是一个多功能性工具程序,支持后台管理、上…

大学教材《C语言程序设计》(浙大版)课后习题解析 | 第十一、十二章

概述 本文主要提供《C语言程序设计》(浙大版) 第十一、十二章的课后习题解析,以方便同学们完成题目后作为参考对照。 专栏直达链接: 《C语言程序设计》(浙大版)_孟俊宇-MJY的博客-CSDN博客​http://t.csdnimg.cn/ZtcgY 一.第十一章(指针进…

达梦DMHS-Manager工具安装部署

目录 1、前言 1.1、平台架构 1.2、平台原理 2、环境准备 2.1、硬件环境 2.2、软件环境 2.3、安装DMHS 2.3.1、源端DMHS前期准备 2.3.2、源端DMHS安装 2.3.3、目的端DMHS安装 3、DMHS-Manager客户端部署 3.1、启动dmhs web服务 3.2、登录web管理平台 4、添加DMHS实…

Linux系统基础知识

​ 一、Linux基础 1、简介 严格来讲,Linux这个词本身只表示Linux内核,但实际上人们已经习惯了用Linux来形容整个基于Linux内核的操作系统。 2、系统特点 开源(源代码可见)免费(Linux系统)注意:开源不一定免费安全性稳定可移植性好高性能(服务端没有图形页面)3、发…

list的使用

前言 我们前面已经对string和vector进行了学习使用,以及对他们的底层进行了模拟实现!本期我们继续学习STL的另外一个容器---list。 本期内容介绍 什么是list? list的常用接口 什么是list? 还是来看看官方的文档说明! 这里通过…

基于springboot实现教师人事档案管理系统项目【项目源码+论文说明】

基于springboot实现在线商城系统演示 摘要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本ONLY在线商城系统就是在这样的大环境下诞生,其可以帮助管理…

如何客观评价5G的现状?

前几天,在知乎上看到一个帖子,热度挺高: 看了一下帖子的回答,基本上都在骂5G。 作为通信行业从业者,我说说我自己的看法。大家姑且听听,一起交流一下。 我们目前所处的这个时代,有一个很大的特点…

python标准数据类型--列表常用方法

在Python中,列表(List)是一种非常常用的数据类型,用于存储一组有序的元素。Python提供了许多内置方法来操作列表,使得对列表的处理变得非常灵活和便捷。在本篇博客中,我们将介绍一些常用的列表方法&#xf…