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


参考书籍:《Java高并发编程详解》。尊重原创,支持知识付费,以下内容标记有摘抄的为该书内容,如需查看该书的对应知识点,请购买原版书籍。

参考文章列表:


volatile

什么是volatile

volatile和synchronized相比,volatile被称为轻量级锁,并且能实现部分synchronized的语义。它在处理多线程并发的时候主要保证了共享资源的可见性,该功能可以理解为一个线程修改某一个共享变量的时候,另外一个变量可以读到该共享变量的值。

资源无可见性在代码中产生的问题

基于了解程序原理就先上代码观察结果的思想,我们可以通过观察下面这段代码,先对volatile的基本体现有一个了解。

以下代码来自摘抄

public class VolatileFoo {

    final static int MAX = 5;

    static int init_value = 0;
//static volatile int init_value = 0; public static void main(String[] args) { new Thread(() -> {
int localValue = init_value;
while (localValue < MAX) {
if (init_value != localValue) {
System.out.printf("The init_value is update to [%d]\n", init_value);
localValue = init_value;
}
}
}, "Reader").start(); new Thread(() -> {
int localValue = init_value;
while (localValue < MAX) {
System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
init_value = localValue;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Updater").start();
} }

如果我们对volatile没有了解的情况下,我们看到以上代码会觉得这就是实现创建了两个线程不断的去交替输出一个变量的功能。观看代码确实就是localValue不断的变更,直到localValue等于5的时候,while循环才结束。但是我们可以看到以下结果,与这里的结论不符,证明我们的结论有误

实际的输出结果是我们的Updater线程一直在输出,当localValue=5的时候,我们的Updater线程就会结束循环。而我们的Reader线程却一直在执行,并是处于死循环的状态。这里我们可以看到Reader线程读到的localValue的值应该是一直没有改变,也能够明显的看到一个问题,那就是两个线程访问一个变量,Updater修改变量的值后Reader线程并没有获取到这个值的变化。

volatile的初体验

出现以上问题之后,我们可以看到共享变量的可见性它的重要性,解决上面程序的问题其实也比较简单,只需要在上面做一个小修改即可。将init_value使用volatile进行修饰,其他的不变,我们再一次观察输出结果。

通过输出结果我们可以看到,当Updater线程对init_value进行了改变之后,我们的Reader线程有效的观察到了这个变量的变化,并且跟着输出了Reader线程观察到init_value的结果。和上面不一样的在于,这段代码都能有效的结束程序

深入了解volatile,并有效掌握实现的方式还需要去了解->CPU硬件的运行和JMM内存模型。CPU与我们程序运行之间的关系是怎么样的,底层是怎么出现这些错误的使我们了解volatile必不可少需要掌握的要点。

我们编写的程序与CPU执行的关系

在我们的程序运行中,我们一个程序被CPU有效执行的一个过程要经过写入硬盘,内存加载,CPU访问执行。,硬盘、内存和CPU之间又有很大的区别。

  • 硬盘:存储资料和软件等数据的设备,有容量大,断电数据不丢失的特点。也被人们称之为“数据仓库”。我们编写的程序就是存储在硬盘里面
  • 内存:1. 负责硬盘等硬件上的数据与CPU之间数据交换处理;2. 缓存系统中的临时数据。3. 断电后数据丢失。可以称为他就是硬盘和CPU之间的桥梁,并且我们的程序编写完成存储到硬盘中之后,开始执行就会被加载进入内存,并等待CPU对内存进行寻址操作。
  • CPU:中央处理单元(Cntral Pocessing Uit)的缩写,也叫处理器,是计算机的运算核心和控制核心。执行我们编写的代码CPU只是接收到执行指令,然后对内存进行寻址操作。

硬盘、内存和CPU的存取速度是递增的,内存比硬盘要快很多,但是CPU又比内存块很多倍,CPU的存取速度快到内存都跟不上,所以在CPU和内存之间出现了一个新的东西,那就是CPU Cache。cache的容量远远小于主存,因此出现cache miss在所难免,这也是我们为什么会出现数据问题的关键所在。

CPU cache结构及cache操作数据导致数据不一致的问题

CPU中cache的结构我们可以打开任务管理器,点击性能即可以看到。多核CPU的结构与单核相似,但是多了所有CPU共享的L3三级缓存。在多核CPU的结构中,L1和L2是CPU私有的,L3则是所有CPU核心共享的。

在我们的系统中,由于短板效应,导致即时我们CPU速度再快也没有办法发挥它的能力。由于内存的读取速度远低于CPU,所以导致我们的程序执行速度,被内存限制。但是当cache出现之后完全改变了这个情况,它极大的增大了CPU的吞吐量。CPU只需要到cache中进行读取和写入操作即可,cache会在之后将结果同步到内存。但是当多线程情况下就会出现问题,每个线程都有自己的工作内存,本地内存,对应CPU中Cache。当多个线程同时操作一个变量的时候,都会进行读取到CPU Cache当中,然后在同步会主内存,这样就会导致数据结果不一致。也就是我们平时看到的,多线程结果与预期不一致的问题。

Java内存模型

Java的内存模型(Java Memory Mode)制定了Java虚拟机如何与计算机的主存进行工作,理解Java内存模型对于编写并发程序非常重要。在CPU cache当中我们使用文字描述了多线程情况下出现结果不一致情况,这里我们可以通过Java内存模型的图解来更直观的看到这个情况是怎么出现的。

图中线程1的工作内存和线程2的工作内存就是我们上面描述的当有多个线程操作一个变量时,每个线程就会将变量复制一份到自己的工作内存当中。当我们的多线程执行的时候,每一个线程赋值一份变量,都对值进行修改,当共享变量不可见的时候,最终就会导致结果不一致。

并发编程的三个重要特性

  • 原子性
    • 原型性是指一个操作的完整性,要么该操作改变的值或者资源全部成功,要么全部不成功。
  • 有序性
    • 所谓有序性就是指代码在执行过程当中的先后顺序。
  • 可见性
    • 可见性在我们最上面的例子里面就展现了,就是一个线程修改共享变量的值的时候,另外一个线程能够看到这个变量的值被改变。

在我们多线程并发编程当中,它的三大特性是保证并发执行不出现错误的关键,volatile我们目前能够看到在并发编程当中能够保证可见性。除了可见性外还其实它还可以保证有序性,只是不能保证原子性而已。假若能够保证原子性,它和synchronize的作用基本那就是一样的,只是底层的实现原理不一样而已。

volatile如何保证有序性(摘抄)

volatile关键字对顺序性的保证就比较霸道,直接禁止JVM和处理器对volatile关键字修饰的指令重新排序,但是对于volatile前后无依赖关系的指令则可以随便怎么排序。

volatile可见性的底层实现原理

volatile底层的实现其实是通过lock关键字进行实现的,我们可以去获取class的汇编码,当使用volatile修饰和不使用volatile的代码分别获取到class的汇编码,然后进行对比,你会发现标有volatile的变量在进行写操作时,会在前面加上lock质量前缀。而lock指令前缀会做如下两件事

  • 将当前处理器缓存行的数据写回到内存。lock指令前缀在执行指令的期间,会产生一个lock信号,lock信号会保证在该信号期间会独占任何共享内存。lock信号一般不锁总线,而是锁缓存。因为锁总线的开销会很大。
  • 将缓存行的数据写回到内存的操作会使得其他CPU缓存了该地址的数据无效。

volatile和synchronize的区别

  • volatile只能修饰实例变量或者类变量,synchronize只能修饰方法或者语句块
  • volatile无法保证原子性,synchronize能够保证原子性
  • volatile和synchronize都能保证有序性,只是实现方式不一样
  • volatile不会使线程陷入阻塞,synchronize相反

总结

volatile被称为轻量级的synchronize是因为他能够有效的实现并发编程的有序性和可见性。但是同时它有自己的缺点,比如不能保证原子性的问题。部分场景能够直接volatile,比如对线程的唤起和关闭。synchronize虽然能够保证并发编程有点三要素,但是会造成线程阻塞。

volatile关键字的详解-并发编程的体现的更多相关文章

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

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

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

    原文链接: http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java5之前,它是一个 ...

  3. AQS详解,并发编程的半壁江山

    千呼万唤始出来,终于写到AQS这个一章了,其实为了写这一章,前面也是做了很多的铺垫,比如之前的 深度理解volatile关键字 线程之间的协作(等待通知模式) JUC 常用4大并发工具类 CAS 原子 ...

  4. 剑指Offer——线程同步volatile与synchronized详解

    (转)Java面试--线程同步volatile与synchronized详解 0. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现 ...

  5. ava下static关键字用法详解

    Java下static关键字用法详解 本文章介绍了java下static关键字的用法,大部分内容摘自原作者,在此学习并分享给大家. Static关键字可以修饰什么? 从以下测试可以看出, static ...

  6. [转]Hadoop集群_WordCount运行详解--MapReduce编程模型

    Hadoop集群_WordCount运行详解--MapReduce编程模型 下面这篇文章写得非常好,有利于初学mapreduce的入门 http://www.nosqldb.cn/1369099810 ...

  7. 详解Python编程中基本的数学计算使用

    详解Python编程中基本的数学计算使用 在Python中,对数的规定比较简单,基本在小学数学水平即可理解. 那么,做为零基础学习这,也就从计算小学数学题目开始吧.因为从这里开始,数学的基础知识列位肯 ...

  8. Java并发编程1--synchronized关键字用法详解

    1.synchronized的作用 首先synchronized可以修饰方法或代码块,可以保证同一时刻只有一个线程可以执行这个方法或代码块,从而达到同步的效果,同时可以保证共享变量的内存可见性 2.s ...

  9. Java并发——线程同步Volatile与Synchronized详解

    0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...

随机推荐

  1. springboot配置文件外置处理

    前言: 在springboot项目中,一般的配置文件都在resource/config下面,它可以以两种方式存在,一种是yml,一种是properties方式. 当运维和开发分开的时候,比如连接mys ...

  2. Python C3 算法 手动计算顺序

    Python C3 算法 手动计算顺序   手动计算类继承C3算法原则: 以所求类的直接子类的数目分成相应部分 按照从左往右的顺序依次写出继承关系 继承关系第一个第一位,在所有后面关系都是第一个出现的 ...

  3. Bzoj1972: [Sdoi2010]猪国杀 题解(大模拟+耐心+细心)

    猪国杀 - 可读版本 https://mubu.com/doc/2707815814591da4 题目可真长,读题都要一个小时. 这道题很多人都说不可做,耗时间,代码量大,于是,本着不做死就不会死的精 ...

  4. css的优先级 和 权重问题 以及 !important 优先级

    css的优先级 和 权重问题 以及 !important 优先级 css选择有多少种? 行内样式(style="") id选择器(#) class选择器(类,伪类) 元素选择器(标 ...

  5. python基础一整型、bool、字符串

    1整型.bool.字符串 1.整型 --数字(int) ​ 用于比较运算的 ​ 32位 -2 ** 31 ~ 2 ** 31 -1 ​ 64位 -2 ** 63 ~ 2 ** 63 -1 ​ 基础运算 ...

  6. Cocos2d-x 3.x 学习笔记(6):Sprite SpriteFrameCache Texture2D TextureCache

    1. 概述 TextureCache是对Texture2D纹理的缓存,SpriteFrameCache是对SpriteFrame的缓存,每个SpriteFrame是对Texture2D的封装,Spri ...

  7. Windows 设置自启动计划任务(非登录启动)

    原因:服务器会不定期重启,且重启后无人看管,不会有人去登录系统.导致我们做的一些开机启动程序失效,进而系统瘫痪. 解决方法: 自己理解,想要达到目的有两种方式:系统服务 & 计划任务配置. 计 ...

  8. 十一、SQL Server CONVERT() 函数

    定义和用法 CONVERT() 函数是把日期转换为新数据类型的通用函数. CONVERT() 函数可以用不同的格式显示日期/时间数据. 语法 CONVERT(data_type(length),dat ...

  9. Java设计模式——工厂设计模式

    工厂模式:主要用来实例化有共同接口的类,工厂模式可以动态决定应该实例化那一个类.工厂模式的形态工厂模式主要用一下几种形态:1:简单工厂(Simple Factory).2:工厂方法(Factory M ...

  10. 带新手玩转MVC——不讲道理就是干(上)

    带新手玩转MVC——不讲道理就是干(上) 前言:这几天更新了几篇博客,都是关于Servlet.JSP的理解,后来又写了两种Web开发模式,发现阅读量还可以,说明JSP还是受关注的,之前有朋友评论说JS ...