Java并发编程——volatile关键字
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java并发编程——volatile关键字,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含7449字,纯文字阅读大概需要11分钟。
内容图文
![Java并发编程——volatile关键字](/upload/InfoBanner/zyjiaocheng/731/f5eeeffffef1464cab741965f5be172c.jpg)
Java并发编程——volatile关键字
概述
在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性、可见性和有序性。只要有一条原则没有被保证,就有可能会导致程序运行不正确。volatile关键字 被用来保证可见性,即保证共享变量的内存可见性以解决缓存一致性问题。一旦一个共享变量被 volatile关键字 修饰,那么就具备了两层语义:内存可见性和禁止进行指令重排序。在多线程环境下,volatile关键字 主要用于及时感知共享变量的修改,并使得其他线程可以立即得到变量的最新值。
volatile关键字 是与 内存模型 紧密相关,先介绍内存模型的相关概念,在开始介绍volatile关键字。
一、内存模型
程序运行过程中的临时数据是存放在主存(物理内存)当中的,计算机在CPU中执行命令时势必会涉及到数据的读取和写入,由于CPU的执行速度很快,而从内存读取数据和向内存中写入数据的指令要慢很多,如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。CPU中就有了高速缓存(寄存器);
也就是说,在程序运行过程中,会将运算需要的数据从主存复制一份到 CPU 的高速缓存当中,那么, CPU 进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。
在多核 CPU 中,每个线程可能运行于不同的 CPU 中,因此每个线程运行时有自己的高速缓存。如果一个变量在多个CPU中都存在缓存,那就可能存在缓存不一致的问题;
解决缓存不一致的方式,在硬件层面:
- 总线加LOCK#锁(软件层面效果等价于使用 synchronized 关键字);
- 通过 缓存一致性协议(在软件层面,效果等价于使用 volatile 关键字)
总线加LOCK#锁,阻塞了其他 CPU 对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存,效率低下;
缓存一致性协议:当 CPU 写数据时,如果发现操作的变量是共享变量,即在其他 CPU 中也存在该变量的副本,会发出信号通知其他 CPU 将该变量的缓存行置为无效状态。因此,当其他 CPU 需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取;
二、并发编程的三个概念
并发编程中存在三个问题:原子性问题、可见性问题和有序性问题;
1、原子性
一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;
2、可见性
当多个线程访问同一个共享变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值;
3、有序性
程序执行的顺序按照代码的先后顺序执行;
指令重排序(Instruction Reorder):
处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的(单线程情形下)。
指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。也就是说,要想使并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
三、Java内存模型
在 Java虚拟机规范 中,试图定义一种 Java内存模型(Java Memory Model,JMM) 来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果;在 Java内存模型 中,也会存在缓存一致性问题和指令重排序的问题。
Java内存模型 规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作,并且每个线程不能访问其他线程的工作内存。
1、原子性
在 Java 中,对基本数据类型的变量的 读取 和 赋值 操作是原子性操作,即这些操作是不可被中断的 : 要么执行,要么不执行
x = 10; //语句1
y = x; //语句2
x++; //语句3
x = x + 1; //语句4
以上四个语句中只有语句1是原子性操作,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作;
2、可见性
对于可见性,Java 提供了 volatile关键字 来保证可见性;
当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值;
3、有序性
在 Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则;
如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序;
happens-before原则(先行发生原则):
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;这个规则只是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性;
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;无论在单线程还是多线程下,同一个锁如果处于被锁定的状态,必须先释放锁,后面才能继续lock操作;
- volatile 变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
- 传递规则:如果操作 A 先行发生于操作 B,而操作 B 又先行发生于操作 C,则可以得出操作 A 先行发生于操作 C ;体现 happens-before 原则具备传递性。
四、剖析volatile关键字
4.1 volatile关键字的两层含义
被volatile关键字修饰后具备两层含义:
- 保证了不同线程对共享变量进行操作时的可见性,即一个线程修改了某个变量的值,这个新值对其他线程来说是 立即可见 的;
- 禁止进行指令重排序;
看下面的例子:
线程1先执行,线程2后执行:
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
没有用volatile关键字修饰出现的问题是:
- 线程2 对变量的修改没有立即刷入到主存当中;
- 即使 线程2 对变量的修改立即反映到主存中,线程1 也可能由于没有立即知道 线程2 对stop变量的更新而一直循环下去;
采用volatile关键字修饰之后:
- 使用 volatile 关键字会强制将修改的值立即写入主存;
- 使用 volatile 关键字的话,当 线程2 进行修改时,会导致 线程1 的工作内存中缓存变量stop的缓存行无效;
- 由于 线程1 的工作内存中缓存变量stop的缓存行无效,所以,线程1 再次读取变量stop的值时会去主存读取;
简化一下,通过使用 volatile 关键字,如下图所示,线程会及时将变量的新值更新到主存中,并且保证其他线程能够立即读到该值。这样,线程1 读取到的就是最新的、正确的值;
4.2 volatile能否保证原子性
volatile 关键字能保证可见性没有错,但是没法保证原子性。可见性只能保证每次读取的是最新的值,但是 volatile 没办法保证对变量的操作的原子性。
五、使用volatile关键字场景
synchronized 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率;而 volatile 关键字在某些情况下性能要优于 synchronized,但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。
通常来说,使用 volatile 必须具备以下两个条件:
- 对变量的写操作不依赖于当前值;
- 该变量没有包含在具有其他变量的不变式中;
关键字 volatile 主要使用的场合是:
在多线程环境下及时感知共享变量的修改,并使得其他线程可以立即得到变量的最新值。
1、状态标记量
2、Double-Check(双重检查)
六、总结
关键字volatile 与内存模型紧密相关,是线程同步的轻量级实现,其性能要比 synchronized关键字好。在作用对象和作用范围上, volatile 用于修饰变量,而 synchronized关键字 用于修饰方法和代码块,而且 synchronized 语义范围不但包括 volatile拥有的可见性,还包括volatile 所不具有的原子性,但不包括 volatile 拥有的有序性,即允许指令重排序。因此,在多线程环境下,volatile关键字 主要用于及时感知共享变量的修改,并保证其他线程可以及时得到变量的最新值。可用以下文氏图表示 synchronized 和 volatile语义范围:
常见问题
1、Volatile
-
Volatile关键字的作用
保证内存的可用性;
防止指令重排;
Volatle不保证原子性; -
内存可见性
volatile保证可见性的原理是在每次访问变量时都会进行一次刷新,因此每次访问都是主存中的最新版本,保证了变量修改的实时可见性; -
volatile使用建议
当两个或者多个线程需要访问的成员变量使用volatile,当要访问的变量已经在synchronized代码块中,或者为常量时,没必要使用volatile;由于使用volatile关键字屏蔽掉了JVM中必要的代码优化,效率低,在必要时才使用;
2、Volatile 与synchronized 区别
- volatile不会进行加锁操作,是一种比synchronized更为轻量级的同步机制;
- volatile变量作用类似于同步变量读写操作;
- volatile 不如 synchronized;
- volatile无法同时保证内存可见性和原则性;
内容总结
以上是互联网集市为您收集整理的Java并发编程——volatile关键字全部内容,希望文章能够帮你解决Java并发编程——volatile关键字所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。