前言

  上一章节简单介绍了线程安全以及最基础的保证线程安全的方法,建议大家手敲代码去体会。这一章会提到volatile关键字,虽然看起来很简单,但是想彻底搞清楚需要具备JMM、CPU缓存模型的知识。不要小看这个关键字,它在整个并发包(concurrent包)使用的非常广泛,掌握volatile关键字是非常重要的。

  如果你是一个急性子,请看下面3点就行:

  • 保证了多线程读取变量的可见性,一个线程修改volatile修饰的变量,另外一个线程会立即读取到新的值
  • 禁止指令重排序
  • volatile关键字不会像synchronized关键字一样造成线程阻塞,也就是说无锁

1.1 初识volatile关键字

  我先写一个例子,在主线程启动2个线程,一个线程负责写,一个线程负责读,读写的该变量就是共享变量,那么结果是你想的那样吗?

/**
* volatile第一个演示Demo类。
*
* @author GrimMjx
*/
public class VolatileDemo1 { //i的初始值为0
public static int i;
//i的最大值为3
public static int MAX = 3; public static void main(String[] args) {
//读线程
new Thread(() -> {
int index = i;
while (index < MAX) {
if (i != index) {
System.out.println("i = " + i);
index = i;
}
}
}).start(); //写线程
new Thread(() -> {
int index = i;
while (index < MAX) {
System.out.println("new i = " + ++i);
index = i;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}

  我贴上一份我执行的结果:

new i = 1
i = 1
new i = 2
new i = 3

  程序不会停,需要手动停。那么问题来了,为什么明明写入了i了,读线程还是无法读到新的i的值呢?读线程压根没有感知到i的变化!只要我们把变量i的定义改变一下,那么就可以解决这个问题。

public volatile static int i;

  改好之后再试一下,确实如我们预料的执行了且读线程也不会死循环。只是一个关键字的差别,会发生很大的不同。那接下来请带着疑问去学习。

new i = 1
i = 1
new i = 2
i = 2
new i = 3
i = 3

1.2 机器CPU

  所有的指令都是CPU寄存器完成的,CPU指令的过程中涉及到数据的写入和读取。CPU能访问的所有数据只能是RAM(计算机内存)。虽然CPU频率不断提升,但是RAM访问速度没有很大突破,因此CPU处理速度和内存的访问速度差距巨大,一次主内存的访问通常在几十到几百个甚至上千个时钟周期,一次L1高速缓存的读写需2个左右时钟周期,一次L2高速缓存的读写需要几十个时钟周期。

1.2.1 CPU Cache模型

  可以直观看到两边的速度严重不对等,于是有了在CPU和主内存之间增加缓存,最靠近CPU的缓存成为L1高速缓存,其次是L2,L3和主内存。我们先看一张各级缓存之间响应时间差距,以及内存到底有多慢。

  接下来我们看一下CPU Cache模型:

1.2.2 CPU缓存一致性

  缓存大大提高了访问速度,但是同时也引入了缓存不一致的问题,比如i++;这个操作。具体的过程如下:

  1. 读取主内存的i到CPU Cache中
  2. 将i+1
  3. 将结果写回CPU Cache
  4. 将数据刷新回主内存

  i++在单线程完全不会有问题,但是多线程的时候就会有问题,每个线程都有自己的工作内存(本地内存),如果在2个线程都执行i++;操作,A线程和B线程此时的工作内存中的i都是0,加1之后都变成1。最后经过计算再写入主内存可能结果还是1。这就是缓存不一致问题。如果想要解决这个问题,主流方法是通过缓存一致性协议(MESI协议)。这个协议的大致思想就是如果当CPU在操作Cache中的数据时,其他Cache也存在一份副本,那么会进行如下操作:

  1. 读取操作,不做任何处理,只是将Cache中的数据读取到寄存器
  2. 写入操作,发出信号通知其他CPU将其变量中的Cache line置为无效状态,其他CPU在进行该变量的读取时候不得不到主内存中再次获取

1.3 Java内存模型

  JMM指定了JVM和计算机RAM如何进行工作的,同时也决定了一个线程对共享变量的写入何时对其他线程可见,有以下几个要点:

  • 每个线程都有自己的工作内存,也成为本地内存
  • 工作线程只存储线程对共享变量的副本
  • 线程不能直接操作主内存,只能操作工作内存
  • 工作内存和JMM都是一个抽象的概念,实际并不存在,覆盖了寄存器,编译器优化等等

  主内存和工作内存的关系和CPU与CPUCache之间的关系是非常类似的,所以通过图示和讲解,我们发现理解volatile关键字会比synchronized关键字困难很多,需要了解机器CPU还有JMM。volatile在JDK5以后的concurrent包运用非常广泛,所以掌握volatile关键字很重要。

1.4 深入理解volatile关键字

  说到并发,有三大特性,原子性,有序性和可见性,那我们从三个方面来介绍

1.4.1 原子性

  volatile不具备原子性

  原子性的意思就是在一次操作中,所有的操作全部执行或者都不执行,就像名字一样是不可分割的。Java中,对变量的读取和赋值操作都是源自的,但是多个原子性的操作在一起,不一定是个原子操作。JMM只保证了基本的读取和赋值的原子性,其他的均不保证。说回volatile,如果在上一章节的UnsafeAdd的例子,用volatile修饰变量i,是否可以解决多线程并发问题呢,结果是不可以的,可以自己去试试。

  就像是i++操作,他其实包含了3步骤

  1.从主内存获取i,缓存到工作内存

  2.在工作内存中进行+1

  3.刷回主内存。

  这也就是刚刚说的多个原子性的操作在一起,不一定是个原子操作

  Java中想要保证原子性,需要使用synchronized关键字,concurrent包的lock,原子封装类和循环CAS的方式(原子变量是一种更好的volatile,后面会讲到)

1.4.2 可见性

  volatile具备可见性

  读取:当一个变量被volatile关键字修饰时,当其他线程对此变量进行了修改,则会迫使其他线程的工作内存中的该变量失效,所以必须从主内存重新获取。(使用的是机器指令lock)

  写入:当然是先修改工作内存,修改后立即将其刷新到主内存中。

  Java中volatile,synchronized关键字和显式锁lock都保证可见性

1.4.3 有序性

  volatile具备有序性

  首先volatile遵循happens-before原则:对一个变量的写操作要早于这个变量之后的读操作。也就是说,如果一个变量使用volatile关键字修饰,一个线程对这个变量进行写操作,另外一个线程对这个变量进行读操作。那么写操作肯定要先发生于读操作。

  volatile对顺序性非常霸道,直接禁止JVM和处理器进行指令重排序,但是对于volatile前后无依赖关系的执行可以随便排序。

  Java中volatile,synchronized关键字和显式锁lock都保证有序性

1.5 volatile的正确打开姿势

  • 确保它们所引用状态的可见性
  • 标识一些重要的程序生命周期事件发生(init,destroy)
  • 确保只有一个线程更新变量的值
  • 不会用就不要用:)

Java并发专题(三)深入理解volatile关键字的更多相关文章

  1. Java并发编程(六)volatile关键字解析

    由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识. 一.内存模型的相关概念 Java内存模型规定所有的变量都是存在 ...

  2. 【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...

  3. Java并发编程:JMM和volatile关键字

    转载请标明出处: http://blog.csdn.net/forezp/article/details/77580491 本文出自方志朋的博客 Java内存模型 随着计算机的CPU的飞速发展,CPU ...

  4. Java并发编程——为什么要用volatile关键字

    首发地址 https://blog.leapmie.com/archives/66ba646f/ 日常编程中出现 volatile 关键字的频率并不高,大家可能对 volatile 关键字比较陌生,再 ...

  5. Java并发机制(3)--volatile关键字与内存模型

    Java并发编程:volatile关键字解析及内存模型 个人整理自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3920373.html 1.线程内存模型: ...

  6. 【Java并发编程】从CPU缓存模型到JMM来理解volatile关键字

    目录 并发编程三大特性 原子性 可见性 有序性 CPU缓存模型是什么 高速缓存为何出现? 缓存一致性问题 如何解决缓存不一致 JMM内存模型是什么 JMM的规定 Java对三大特性的保证 原子性 可见 ...

  7. Java并发(3)- 聊聊Volatile

    引言 谈到volatile关键字,大多数开发者都有一定了解,可以说是开发者非常熟悉,深入之后又非常陌生的一个关键字.相当于轻量的synchronized,也叫轻量级锁,与synchronized相比性 ...

  8. 深入理解volatile关键字

    Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作内存 ...

  9. 对精致码农大佬的 [理解 volatile 关键字] 文章结论的思考和寻找真相

    一:背景 1. 讲故事 昨天在园里的编辑头条看到 精致码农大佬 写的一篇题为:[C#.NET 拾遗补漏]10:理解 volatile 关键字 (https://www.cnblogs.com/will ...

随机推荐

  1. BZOJ_4198_[Noi2015]荷马史诗_huffman实现

    BZOJ_4198_[Noi2015]荷马史诗_huffman实现 题意: Allison 最近迷上了文学.她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读她爱不释手的<荷马史诗> ...

  2. IE浏览器下ajax和缓存的那些事儿

    项目经理最近返回了一些问题: (客户浏览器为IE11,本地360,谷歌没发现任何问题) 1.加载页面时下拉框中没有数据,关闭之后再打开出现数据: 2.数据保存之后页面没有刷新: 我也是接手别人的项目, ...

  3. 深度学习与自动驾驶领域的数据集(KITTI,Oxford,Cityscape,Comma.ai,BDDV,TORCS,Udacity,GTA,CARLA,Carcraft)

    http://blog.csdn.net/solomon1558/article/details/70173223 Torontocity HCI middlebury caltech 行人检测数据集 ...

  4. 排序算法——(2)Python实现十大常用排序算法

    上期为大家讲解了排序算法常见的几个概念: 相关性:排序时是否需要比较元素 稳定性:相同元素排序后是否可能打乱 时间空间复杂度:随着元素增加时间和空间随之变化的函数 如果有遗忘的同学可以看排序算法——( ...

  5. 中国.NET:各地微软技术俱乐部汇总(持续更新中...)

    中国.NET:各地微软技术俱乐部汇总(持续更新中...)   本文是转载文,源地址: https://www.cnblogs.com/panchun/p/JLBList.html by ​史记微软. ...

  6. Unity 用ml-agents机器学习造个游戏AI吧(2)(入门DEMO)

    前言: 上一篇博文已经介绍了Unity ml-agents的环境配置(https://www.cnblogs.com/KillerAery/p/10629963.html)了. 个人建议先敲demo再 ...

  7. jQuery拼图小游戏

    jQuery拼图小游戏 最后样式 核心代码部分 <script type="text/javascript" > $(function () { $("td& ...

  8. nginx漏洞分析与升级修复

    一 .此次漏洞分析 1 nginx HTTP/2漏洞 [nginx-announce] nginx安全公告(CVE-2018-16843,CVE-2018-16844)在nginx HTTP / 2实 ...

  9. 【工作查漏补缺】jQuery ajax - serializeArray()

    方法用途: 获取表单内的所有有name的所有数据框,在非表单提交需要挨个遍历组装数据的情况下很好用 ps:需要jQuery支持 var twoform = $("#editProductAc ...

  10. Android之Lottie动画详解

    文章大纲 一.Lottie介绍二.Lottie实战三.项目源码下载四.参考文章   一.Lottie介绍 1. 什么是Lottie   Lottie是Android和iOS的移动库,用于解析Adobe ...