volatile底层原理详解

时间:2022-12-20 13:25:59

今天我们聊聊volatile底层原理;

Java语言规范对于volatile定义如下:

Java编程语言允许线程访问共享变量,为了确保共享变量能够被准确和一致性地更新,线程应该确保通过排它锁单独获得这个变量。

首先我们从定义开始入手,官方定义比较拗口。通俗来说就是一个字段被volatile修饰,Java的内存模型确保所有的线程看到的这个变量值是一致的,但是它并不能保证多线程的原子操作。这就是所谓的线程可见性。我们要知道他是不能保证原子性的

内存模型相关概念

Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的修改何时对另外一个线程可见。JMM定义了线程与主内存的抽象关系:线程之间的变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)保存着共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。

volatile底层原理详解

如果线程A与线程B通信:

  1. 线程A要先把本地内存A中更新过的共享变量刷写到主内存中。

  2. 线程B到主内存中读取线程A更新后的共享变量

计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。

有了CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中。

举个例子:

i++;

当线程运行这行代码时,首先会从主内存中读取i,然后复制一份到CPU高速缓存中,接着CPU执行+1的操作,再将+1后的数据写在缓存中,最后一步才是刷新到主内存中。在单线程时没有问题,多线程就有问题了。

如下:假如有两个线程A、B都执行这个操作(i++),按照我们正常的逻辑思维主存中的i值应该=3,但事实是这样么?

分析如下:

两个线程从主存中读取i的值(1)到各自的高速缓存中,然后线程A执行+1操作并将结果写入高速缓存中,最后写入主存中,此时主存i==2,线程B做同样的操作,主存中的i仍然=2。所以最终结果为2并不是3。这种现象就是缓存一致性问题。

解决缓存一致性方案有两种:

  1. 通过在总线加LOCK#锁的方式;

  2. 通过缓存一致性协议。

但是方案1存在一个问题,它是采用一种独占的方式来实现的,即总线加LOCK#锁的话,只能有一个CPU能够运行,其他CPU都得阻塞,效率较为低下。

第二种方案,缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。所以JMM就解决这个问题。

volatile实现原理

有volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令,该指令在多核处理器下会引发两件事情。

  1. 将当前处理器缓存行数据刷写到系统主内存。

  2. 这个刷写回主内存的操作会使其他CPU缓存的该共享变量内存地址的数据无效。

这样就保证了多个处理器的缓存是一致的,对应的处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器缓存行设置无效状态,当处理器对这个数据进行修改操作的时候会重新从主内存中把数据读取到缓存里。

使用场景

volatile经常用于两个场景:状态标记、double check

  1. 状态标记
//线程1
boolean stop = false;
while(!stop){
doSomething();
} //线程2
stop = true;

这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。

下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。

那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。

但是加上volatile就没问题了。如下所示:

    volatile boolean flag = false;

    while(!flag){
   doSomething();
} public void setFlag() {
   flag = true;
} volatile boolean inited = false;
//线程1:
context = loadContext();  
inited = true;             //线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
  1. double check
public class Singleton{
   private volatile static Singleton instance = null;    private Singleton() {   }    public static Singleton getInstance() {
       if(instance==null) {
           synchronized (Singleton.class) {
               if(instance==null)
                   instance = new Singleton();
          }
      }
       return instance;
  }
}

客官觉得有用请点赞或收藏,关注公众号JavaStorm,你将发现一个有趣的灵魂!

后面我们继续分析JMM内存模型相关技术。

将自己的知识分享,以后会持续输出,希望给读者朋友们带来帮助。若有帮助读者朋友可以点赞或者关注。

volatile底层原理详解

volatile底层原理详解的更多相关文章

  1. Spring Aop底层原理详解

    Spring Aop底层原理详解(来源于csdn:https://blog.csdn.net/baomw)

  2. HBase 底层原理详解(深度好文,建议收藏)

    HBase简介 HBase 是一个分布式的.面向列的开源数据库.建立在 HDFS 之上.Hbase的名字的来源是 Hadoop database,即 Hadoop 数据库.HBase 的计算和存储能力 ...

  3. iptables的概念与底层原理(详解)

    目录 一:iptables 1.iptables简介 2.什么是防火墙? 3.防火墙种类 二:iptables基本介绍 1.解析内容 三:iptables流程(讲解) 1.流入本机 2.解析(流入本机 ...

  4. volatile关键字的详解-并发编程的体现

    xl_echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!! 参 ...

  5. 锁之“轻量级锁”原理详解(Lightweight Locking)

    大家知道,Java的多线程安全是基于Lock机制实现的,而Lock的性能往往不如人意. 原因是,monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是JVM依赖 ...

  6. Influxdb原理详解

    本文属于<InfluxDB系列教程>文章系列,该系列共包括以下 15 部分: InfluxDB学习之InfluxDB的安装和简介 InfluxDB学习之InfluxDB的基本概念 Infl ...

  7. JSPatch实现原理详解&lt&semi;二&gt&semi;

    本文转载至 http://blog.cnbang.net/tech/2855/ 距离上次写的<JSPatch实现原理详解>有一个月的时间,在这段时间里 JSPatch 在不断地完善和改进, ...

  8. 图解SynchronousQueue原理详解-非公平模式

    SynchronousQueue原理详解-非公平模式 开篇 说明:本文分析采用的是jdk1.8 约定:下面内容中Ref-xxx代表的是引用地址,引用对应的节点 前面已经讲解了公平模式的内容,今天来讲解 ...

  9. Netty学习——服务器端代码和客户端代码 原理详解

    服务器端代码和客户端代码 原理详解:(用到的API) 0.Socket 连接服务器端的套接字 1.TcompactProtocol   协议层2.TFrameTransport   传输层3.THsh ...

随机推荐

  1. Kindle支持哪些格式

    官方产品介绍页面有相关技术参数: Kindle Format 8 (AZW3), Kindle (AZW), TXT,PDF, MOBI, PRC原格式,HTML,DOC,DOCX,JPEG,GIF, ...

  2. bootstrap-14

    基础导航条: 使用方法:1. 首先在制作导航的列表(<ul class="nav">)基础上添加类名"navbar-nav" 2.在列表外部添加一个 ...

  3. JZ2440开发笔记(9)——位置无关代码设计【转】

    b MAIN 和 ldr pc,=MAIN 的区别(谈到代码位置无关性) 看bootloader的时候经常看到这两种写法,不太明白区别,网上查了查.其实看了之后还是一头雾水? 其中,2和3 似乎是一个 ...

  4. Oracle使用笔记(二)

    一.表空间 1.创建表空间 create tablespace db_test --表空间名 datafile 'D:\oracle\product\11.2.0\dbhome_1\oradata\o ...

  5. CentOS7为firewalld添加开放端口及相关操作

    1.firewalld的基本使用 启动: systemctl start firewalld 查看状态: systemctl status firewalld 停止: systemctl disabl ...

  6. Android BLE设备蓝牙通信框架BluetoothKit

    BluetoothKit是一款功能强大的Android蓝牙通信框架,支持低功耗蓝牙设备的连接通信.蓝牙广播扫描及Beacon解析. 关于该项目的详细文档请关注:https://github.com/d ...

  7. RabbitMQ文档翻译——Hello World&excl;(下)

    Receiving That's it for our sender. Our receiver is pushed messages from RabbitMQ, so unlike the sen ...

  8. JDK并发工具之同步控制

    一.synchronized的功能扩展:重入锁(java.util.concurrent.locks.ReentrantLock) 重入锁可以完全替代synchronized关键字.在JDK 5.0的 ...

  9. 2018&period;07&period;06 洛谷P2936 &lbrack;USACO09JAN&rsqb;全流Total Flow(最大流)

    P2936 [USACO09JAN]全流Total Flow 题目描述 Farmer John always wants his cows to have enough water and thus ...

  10. HDUOJ--1874 畅通工程续

    畅通工程续 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submi ...