前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >voliate工作实际应用场景

voliate工作实际应用场景

作者头像
公众号 IT老哥
修改于 2020-09-21 07:06:29
修改于 2020-09-21 07:06:29
65800
代码可运行
举报
运行总次数:0
代码可运行

本文源自 公-众-号 IT老哥 的分享

哈喽大家好,我是IT老哥,今天我们来讲讲面试必问的voliate

单线程的情况下呢,我们肯定用不到这个voliate

只有在多线程的情景下才能用到,文章结尾我会举一个经典的案例

voliate三特性

  • 保证可见性;
  • 不保证复合操作的原子性;
  • 禁止指令重排。

第一:可见性

先给大家介绍一下JMM的内存模型

我们定义的共享变量就是存在主内存中,每个线程内的变量是在工作内存中操作的,当一个线程A修改了主内存里的一个共享变量,这个时候线程B是不知道这个值已经修改了,因为线程之间的工作内存是互相不可见的

那么这个时候voliate的作用就是让A、B线程可以互相感知到对方对共享变量的修改,当线程A更新了共享数据,会将数据刷回到主内存中,而线程B每次去读共享数据时去主内存中读取,这样就保证了线程之间的可见性

这种保证内存可见性的机制是:内存屏障(memory barrier)

内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。 内存屏障有两个作用:

1.阻止屏障两侧的指令重排序; 2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

这里不深入说这个机制了,对于大家目前的情况可能理解起来比较困难

第二:不保证复合操作的原子性

1、什么叫原子性?

所谓原子性,就是说一个操作不可被分割或加塞,要么全部执行,要么全不执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
i = 0;            ---1
j = i ;           ---2
i++;              ---3
i = j + 1;        ---4

上面四个操作,有哪个几个是原子操作,那几个不是?如果不是很理解,可能会认为都是原子性操作,其实只有1才是原子操作,其余均不是。

  • 1---在Java中,对基本数据类型的变量和赋值操作都是原子性操作;
  • 2---包含了两个操作:读取i,将i值赋值给j
  • 3---包含了三个操作:读取i值、i + 1 、将+1结果赋值给i;
  • 4---同三一样

Java只保证了基本数据类型的变量和赋值操作才是原子性的(注:在32位的JDK环境下,对64位数据的读取不是原子性操作*,如long、double)

第三:有序性(禁止jvm对代码进行重排序)

有序性:即程序执行的顺序按照代码的先后顺序执行。

一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:

  1. 在单线程环境下不能改变程序运行的结果;
  2. 存在数据依赖关系的不允许重排序

第四:举个非常常见的voliate用法—DCL

什么是DCL呢,其实就是double check lock的简写

DCL很多人都在单利中用过,如下这种写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Singleton {
   private static Singleton singleton;
   private Singleton(){}

   public static Singleton getInstance(){
       if(singleton == null){                              // 1
           synchronized (Singleton.class){                 // 2
               if(singleton == null){                      // 3
                   singleton = new Singleton();            // 4
               }
           }
       }
       return singleton;
   }
}

表面上这个代码看起来很完美,但是其实有问题

先说一下他完美的一面吧:

1、如果检查第一个singleton不为null,则不需要执行下面的加锁动作,极大提高了程序的性能;

2、如果第一个singleton为null,即使有多个线程同一时间判断,但是由于synchronized的存在,只会有一个线程能够创建对象;

3、当第一个获取锁的线程创建完成后singleton对象后,其他的在第二次判断singleton一定不会为null,则直接返回已经创建好的singleton对象;

但是到底是哪里有错误呢,听老哥细细分析

首先创建一个对象分为三个步骤:

1、分配内存空间

2、初始化对象

3、讲内存空间的地址赋值给对象的引用

但是上面我讲了,jvm可能会对代码进行重排序,所以2和3可能会颠倒,

就会变成 1 —> 3 —> 2的过程,

那么当第一个线程A抢到锁执行初始化对象时,发生了代码重排序,3和2颠倒了,这个时候对象对象还没初始化,但是对象的引用已经不为空了,

所以当第二个线程B遇到第一个if判断时不为空,这个时候就会直接返回对象,但此时A线程还没执行完步骤2(初始化对象)。就会造成线程B其实是拿到一个空的对象。造成空指针问题。

解决方案:

既然上面的问题是由于jvm对代码重排序造成的,那我们禁止重排序不就好了吗?

voliate刚好可以禁止重排序,所以改造后的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

public class Singleton {
   //通过volatile关键字来确保安全
   private volatile static Singleton singleton;

   private Singleton(){}

   public static Singleton getInstance(){
       if(singleton == null){
           synchronized (Singleton.class){
               if(singleton == null){
                   singleton = new Singleton();
               }
           }
       }
       return singleton;
   }
}

这样就不会存在2和3颠倒的问题了

解决方案二:

基于类初始化

该解决方案的根本就在于:利用classloder的机制来保证初始化instance时只有一个线程。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Singleton {
   private static class SingletonHolder{
       public static Singleton singleton = new Singleton();
   }

   public static Singleton getInstance(){
       return SingletonHolder.singleton;
   }
}

这种解决方案的实质是:允许步骤2和步骤3重排序,但是不允许其他线程看见。

晚安,兄弟们!

云服务器云硬盘数据库(包括MySQLRedisMongoDBSQL Server),CDN流量包,短信流量包,cos资源包,消息队列ckafka,点播资源包,实时音视频套餐,网站管家(WAF),大禹BGP高防(包含高防包及高防IP),云解析SSL证书,手游安全MTP移动应用安全云直播等等。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
给个[在看],是对IT老哥最大的支持
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT老哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
死磕juc(五)volatile与Java内存模型
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。
yuanshuai
2022/09/26
3010
死磕juc(五)volatile与Java内存模型
你真的了解 volatile 关键字吗?
The Java programming language provides a secondmechanism, volatile fields, that is more convenient than locking for somepurposes. A field may be declared volatile, in which case the Java Memory Modelensures that all threads see a consistent value for the variable.
武培轩
2019/10/31
8720
java中volatile关键字
1.可见性: 当一个线程修改了volatile修饰的变量的值,其他线程可以立即看到这个修改,保证了共享变量的可见性。
终有救赎
2023/10/16
2530
java中volatile关键字
(四) 一文搞懂 JMM - 内存模型
用户7630333
2023/12/07
3.2K0
(四) 一文搞懂 JMM - 内存模型
java并发线程实战(1) 线程安全和机制原理
多个线程同时执行也能工作的代码就是线程安全的代码 如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的
黄规速
2022/04/14
7040
java并发线程实战(1) 线程安全和机制原理
Java多线程内存模型(JMM)
CPU在摩尔定律的指导下以每18个月起一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算需要高速的数据。
chenchenchen
2022/01/05
4010
Java多线程内存模型(JMM)
volatile与JMM
写完后 立即刷新回主内存并及时发出通知,大家可以去主内存拿最新版,前面的修改对后面所有线程可见
鱼找水需要时间
2023/02/16
2460
volatile与JMM
Java程序员面试必备:Volatile全方位解析
volatile是Java程序员必备的基础,也是面试官非常喜欢问的一个话题,本文跟大家一起开启volatile学习之旅,如果有不正确的地方,也麻烦大家指出哈,一起相互学习~
捡田螺的小男孩
2020/08/13
6070
Java程序员面试必备:Volatile全方位解析
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
在多线程编程中,你是否遇到过变量值莫名“消失”​、线程间数据不同步,甚至单例模式失效的诡异问题?💡 其实,这些问题的根源往往在于对 ​JMM(Java Memory Model,Java内存模型)​ 的理解不够深入!
摘星.
2025/05/20
900
【云+社区年度征文】深入理解Volatile关键字和使用
背景:计算机在执行程序时,每条指令都是由CPU调度执行的。CPU执行计算指令时,产生与内存(物理内存)通讯的过程(即数据的读取和写入),由于CPU执行速度很快,而从内存读取数据和内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存(Cache)。
沁溪源
2020/12/19
3390
【云+社区年度征文】深入理解Volatile关键字和使用
【Java线程】深入理解Volatile关键字和使用
理解volatile底层原理之前,首先介绍关于缓存一致性协议的知识。 背景:计算机在执行程序时,每条指令都是由CPU调度执行的。CPU执行计算指令时,产生与内存(物理内存)通讯的过程(即数据的读取和写入),由于CPU执行速度很快,而从内存读取数据和内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存(Cache)。
沁溪源
2020/12/22
4390
【Java线程】深入理解Volatile关键字和使用
深入分析Volatile的实现原理
在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小,下面我们将深入分析Voliate的实现原理。
heasy3
2020/08/01
1.8K0
谈谈volatile
volatile通常被比喻成“轻量级的synchronized”,也是Java并发编程中比较重要的一个关键字。和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量,无法修饰方法或代码块等。 volatile的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了。
Java阿呆
2020/11/04
4640
谈谈volatile
voliate关键字原理
被volatile修饰的变量在编译成字节码文件时会多个lock指令,该指令在执行过程中会生成相应的内存屏障,以此来解决可见性跟重排序的问题。
全栈程序员站长
2022/08/15
6730
voliate关键字原理
java面试题:voliate底层原理——详解
注意:基于 CPU 缓存一致性协议,JVM 实现了 volatile 的可见性,但由于总线嗅探机制,会不断的监听总线,如果大量使用 volatile 会引起总线风暴。所以,volatile 的使用要适合具体场景。
全栈程序员站长
2022/08/31
2.6K0
java面试题:voliate底层原理——详解
聊聊并发编程:volatile关键字
上一篇学习了synchronized的关键字,synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁,而volatile是一个轻量级的同步机制。
烂猪皮
2023/09/04
2300
聊聊并发编程:volatile关键字
Java并发篇:volatile关键字吐血整理「建议收藏」
在上一篇文章中我们已经知道线程是 通过主内存 去进行线程间的 隐式通信 的,而线程对共享变量的写操作在 工作内存 中完成,由JMM控制 共享变量由工作内存写回到主内存的时机 。
全栈程序员站长
2022/09/08
4700
并发编程特性与volatile
由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
程序员NEO
2023/09/30
3300
并发编程特性与volatile
Java并发编程之 Java内存模型
JMM 即 Java Memory Model ,它从Java层面定义了 主存、工作内存 抽象概念,底层对应着CPU 寄存器、缓存、硬件内存、CPU 指令优化等。JMM 体现在以下几个方面:
Java微观世界
2025/01/21
890
Java并发编程之 Java内存模型
JUC 多线程之 volatile 关键字
但是JMM ( java内存模型 )规范必须保证:内存可见性,禁止指令重排(有序性),原子性。
万能青年
2019/08/30
4050
相关推荐
死磕juc(五)volatile与Java内存模型
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档