再有人问你volatile是什么,就把这篇文章发给他
转载自 再有人问你volatile是什么,就把这篇文章发给他
在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized、volatile、final、concurren包等。在前一篇文章中,我们也介绍了synchronized的用法及原理。本文,来分析一下另外一个关键字——volatile。
本文就围绕volatile
展开,主要介绍volatile
的用法、volatile
的原理,以及volatile
是如何提供可见性和有序性保障的等。
volatile
这个关键字,不仅仅在Java语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。尤其在C语言、C++以及Java中,都有volatile
关键字。都可以用来声明变量或者对象。下面简单来介绍一下Java语言中的volatile
关键字。
volatile的用法
volatile
通常被比喻成"轻量级的synchronized
",也是Java并发编程中比较重要的一个关键字。和synchronized
不同,volatile
是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
volatile
的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile
修饰就可以了。
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
}
如以上代码,是一个比较典型的使用双重锁校验的形式实现单例的,其中使用volatile
关键字修饰可能被多个线程同时访问到的singleton。
volatile的原理
在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,为了提高处理器的执行速度,在处理器和内存之间增加了多级缓存来提升。但是由于引入了多级缓存,就存在缓存数据不一致问题。
但是,对于volatile
变量,当对volatile
变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中。
但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议
缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
所以,如果一个变量被volatile
所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile
在并发编程中,其值在多个缓存中是可见的。
volatile与可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
我们在再有人问你Java内存模型是什么,就把这篇文章发给他中分析过:Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。
前面的关于volatile
的原理中介绍过了,Java中的volatile
关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile
来保证多线程操作时变量的可见性。
volatile与有序性
有序性即程序执行的顺序按照代码的先后顺序执行。
我们在再有人问你Java内存模型是什么,就把这篇文章发给他中分析过:除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load->add->save
有可能被优化成load->save->add
。这就是可能存在有序性问题。
而volatile
除了可以保证数据的可见性之外,还有一个强大的功能,那就是他可以禁止指令重排优化等。
普通的变量仅仅会保证在该方法的执行过程中所依赖的赋值结果的地方都能获得正确的结果,而不能保证变量的赋值操作的顺序与程序代码中的执行顺序一致。
volatile可以禁止指令重排,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被volatile
修饰的变量的操作,会严格按照代码顺序执行,load->add->save
的执行顺序就是:load、add、save。
volatile与原子性
原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。
我们在Java的并发编程中的多线程问题到底是怎么回事儿中分析过:线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。
在上一篇文章中,我们介绍synchronized
的时候,提到过,为了保证原子性,需要通过字节码指令monitorenter
和monitorexit
,但是volatile
和这两个指令之间是没有任何关系的。
所以,volatile
是不能保证原子性的。
在以下两个场景中可以使用volatile
来代替synchronized
:
1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程会修改变量的值。
2、变量不需要与其他状态变量共同参与不变约束。
除以上场景外,都需要使用其他方式来保证原子性,如synchronized
或者concurrent包
。
我们来看一下volatile和原子性的例子:
public class Test {public volatile int i = 0;public void increase() {i++;}public static void main(String[] args) {final Test test = new Test();for(int i=0;i<10;i++){new Thread(){public void run() {for(int j=0;j<1000;j++)test.increase();};}.start();}while(Thread.activeCount()>1) //保证前面的线程都执行完Thread.yield();System.out.println(test.i);}
}
以上代码比较简单,就是创建10个线程,然后分别执行1000次i++
操作。正常情况下,程序的输出结果应该是10000,但是,多次执行的结果都小于10000。这其实就是volatile
无法满足原子性的原因。
为什么会出现这种情况呢,那就是因为虽然volatile可以保证i
在多个线程之间的可见性。但是无法保证i++
的原子性。
i++
操作,一共有三个步骤:load i
,add i
,save i
。在多线程场景中,如果这三个步骤无法按照顺序执行的话,那么就会出现问题。
如上图,两个线程同时执行i++
操作,如果允许指令重排,我们期望的结果是3,但是实际执行结果可能是2,甚至可能是1。
总结与思考
我们介绍过了volatile
关键字和synchronized
关键字。现在我们知道,synchronized
可以保证原子性、有序性和可见性。而volatile
却只能保证有序性和可见性。
那么,我们再来看一下双重校验锁实现的单例,已经使用了synchronized
,为什么还需要volatile
?
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
}
答案,我们在下一篇文章:既生synchronized,何生volatile中介绍,敬请期待。
再有人问你volatile是什么,就把这篇文章发给他相关推荐
- 再有人问你volatile是什么,把这篇文章也发给他(深入分析)
转载自 再有人问你volatile是什么,把这篇文章也发给他 在上一篇文章中,我们围绕volatile关键字做了很多阐述,主要介绍了volatile的用法.原理以及特性.在上一篇文章中,我提到过:v ...
- 再有人问你volatile是什么,把这篇文章也发给他。
在上一篇文章中,我们围绕volatile关键字做了很多阐述,主要介绍了volatile的用法.原理以及特性.在上一篇文章中,我提到过:volatile只能保证可见性和有序性,无法保证原子性.关于这部分 ...
- 再有人问 Java 中的注解就把这篇文章丢给他!
什么是注解? 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据.所以,可以说注解就是源代码的元数据.比如,下面这段代码: @Override public String toString() ...
- 再有人问你volatile是什么,就把这篇文章发给他,让他哑口无言
本文就围绕volatile展开,主要介绍volatile的用法.volatile的原理,以及volatile是如何提供可见性和有序性保障的等. volatile这个关键字,不仅仅在Java语言中有,在 ...
- 再有人问你synchronized是什么,就把这篇文章发给他。
在<深入理解Java虚拟机>中,有这样一段话: synchronized关键字在需要原子性.可见性和有序性这三种特性的时候都可以作为其中一种解决方案,看起来是"万能"的 ...
- 再有人问你Java内存模型是什么,就把这篇文章发给他
前几天,发了一篇文章,介绍了一下JVM内存结构.Java内存模型以及Java对象模型之间的区别.有很多小伙伴反馈希望可以深入的讲解下每个知识点.Java内存模型,是这三个知识点当中最晦涩难懂的一个,而 ...
- 再有人问你MySQL索引原理,就把这篇文章甩给他!
△Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 335 篇原创分享 作者 l zyz1992 来源 l Hollis(ID:hollischuang) 索引,可能让好很多 ...
- 再有人问你Java内存模型是什么,就把这篇文章发给他。
前几天,发了一篇文章,介绍了一下JVM内存结构.Java内存模型以及Java对象模型之间的区别.有很多小伙伴反馈希望可以深入的讲解下每个知识点.Java内存模型,是这三个知识点当中最晦涩难懂的一个,而 ...
- redisson的锁的类型_再有人问你分布式锁是什么,就把这个丢给他!
[小宅按]现在面试都会聊聊分布式系统,通常面试官都会从服务框架(Spring Cloud.Dubbo),一路聊到分布式事务.分布式锁.ZooKeeper 等知识.今天就来聊聊分布式锁这块的知识,先具体 ...
最新文章
- Python 3.8即将发布,这几个变化你必须知道
- 数据挖掘导论读书笔记9聚类分析
- httphandlers 与 httpmodules
- Dx11DemoBase 基类(二) 初始化 DirectX11 的 4个基本步骤
- Java 网关-Servlet Gateway
- SpringBoot2.1.9 多数据源Mybatis—JDBC配置
- EOJ3536 求蛇形矩阵每一行的和---找规律
- 2021-06-13list map set 并发问题
- js文件里获取路由 vue_纯js文件中,怎么使用vue的路由
- 手眼标定(eye in hand)-步骤
- Linux从入门到指令详解
- 在命令行中创建Django项目
- 【周总结】博客第一周小结SSL暑假训练第二周小结
- php 相加函数,求和函数是什么
- PAT乙级题解——1093 字符串A+B (20分)
- 网页服务器怎么做链接,如何做网页超级链接
- 封装自己的js库(二)---仿照JQuery
- 蓬莱小课:零基础应届生如何转行学数据分析师?
- 孙鑫老师MFC视频使用的MSDN帮助文档下载及安装
- Execution failed for task ':app:processDebugGoogleServices'. No matching client found for package
热门文章
- C++中输入一组数字 以换行符结束输入
- 「offer来了」快来关注这些性能优化问题
- [Redis6]常用数据类型_List列表
- [Spring5]IOC容器_Bean管理_工厂Bean
- 前缀和,差分算法训练
- 《C++ Primer》7.3.1节练习
- 最长回文 HDU - 3068(求最长回文串的长度【马拉车算法Manacher】)
- c语言火车票管理系统源代码2000行,C语言教务管理系统[2000行代码].doc
- 数据结构---二叉排序树
- Spring Cloud——Consul——架构体系