volatile深入理解

参考网页

https://www.cnblogs.com/dolphin0520/p/3920373.html

https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

https://blog.csdn.net/dl88250/article/details/5439024

http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization

volatile存在的意义——一些背景知识

共享变量--可以被多个线程访问的变量

可以被多个线程访问的变量称为共享变量。

多线程场景下讨论共享变量才有意义。

多线程场景可以是多CPU,也可以是单个CPU,只不多单个CPU是以线程调度的形式执行的多线程。

CPU高速缓存的引入

【CPU从内存读取数据的速度】和【CPU向内存写入数据速度】比【CPU执行指令的速度】要慢很多,因此如果任何时候CPU对数据的操作都要通过和内存的交互来进行,会大大降低CPU指令执行的速度。因此在CPU里面就有了【高速缓存】。

程序运行时,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

volatile存在的意义——并发场景(多线程场景)下的问题

缓存一致性问题

缓存一致性问题的产生--【多线程】场景下的【共享变量】存储在【CPU高速缓存】,【因为各个线程的操作导致各个共享变量副本不一致】,这就是缓存一致性问题

共享变量在多个CPU的高速缓存中存在副本(一般在多线程编程时才会出现),多个线程同一时间段内对共享变量进行操作,就可能存在缓存一致性问题。

缓存一致性问题的解决方案

通常来说有以下2种解决方法:

1)总线加LOCK#锁

2)缓存一致性协议

这2种方式都是硬件层面上提供的方式。

CPU指令重排序

单线程下保证重排序后的最终执行结果和代码顺序执行的结果是一致的

一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

重排序时会考虑指令之间的数据依赖性

处理器在进行重排序时会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。

指令重排序不会影响单个线程的执行,但是会影响到并发线程执行的正确性

CPU指令重排序产生问题的原因--【多线程场景】下【对共享变量操作】的协调

硬件层面上CPU为了提升自身执行性能对指令进行了优化和重排序。

多线程场景下CPU指令重排序可能会影响并发程序的执行,导致问题的根本原因还是【多线程场景】下【对共享变量操作】的协调上产生了问题。如何协调好?这个要依据具体的业务场景。volatile可以禁止指令重排序,在需要禁止指令重排序时可以使用volatile。

说明volatile,为啥要提【缓存一致性问题】和【CPU指令重排序】?

【缓存一致性问题】和【CPU指令重排序】实际上对应的是并发编程中的【可见性】和【有序性】的问题。后面可以看到,volatile实际上就可以解决并发编程时【可见性】和【有序性】的问题。

后面会讲到,并发编程涉及【原子性】、【可见性】和【有序性】三方面的问题,只有三方面的问题都能解决,才能保证并发程序正确执行。

volatile可以解决【可见性】和【有序性】的问题,但是无法解决【原子性】的问题。volatile能解决的问题也决定了volatile的应用场景。

并发编程需要满足操作的原子性、可见性和有序性【都是为了正确操作共享变量】

原子性

就是要保证对一个事务要进行【完整的】、【不可中断】的操作。

何为事务?事务包括一个操作或者多个操作。一个事务所包含的所有操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

可见性

多个线程的共享变量。当一个线程对共享变量修改时,其他线程能够立即看得到修改的值。

缓存一致性问题就导致共享变量的可见性出现问题。也就是说多CPU下各个CPU高速缓存中共享变量的副本不一致,就会导致各个CPU见到的共享变量不一致,这样共享变量的可见性就出了问题。

有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。

因为CPU会对指令进行重排序,所以最终的指令执行顺序在CPU硬件层面不一定严格按照java代码层面的顺序执行。

考虑单线程和多线程两种情形。单线程下CPU指令重排序但是仍能保证指令逻辑顺序的正确性。

但是多线程下,由于共享变量的存在,可能导致不可预知的、不可预期的、引起混乱的问题。

这就要求程序员理解这些,并采用技术手段保证多线程下程序仍可按照正确的逻辑运行。

★总结【并发程序正确执行的条件】

要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致并发程序运行不正确。

volatile关键字--保证可见性和有序性,但是无法保证原子性

volatile关键字的两层语义:简单来说就是volatile保证了可见性和有序性

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2)禁止进行指令重排序。

volatile保证原子性吗

https://my.oschina.net/u/3866531/blog/1936097

volatile能保证有序性吗--内存屏障,禁止指令重排序

在前面提到volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。

volatile关键字禁止指令重排序有两层意思:

1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

★volatile的原理和实现机制

前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。

下面这段话摘自《深入理解Java虚拟机》:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;禁止指令重排序

2)它会强制将对缓存的修改操作立即写入主存

3)如果是写操作,它会导致其他CPU中对应的缓存行无效

volatile的使用

使用volatile必须具备以下2个条件

https://my.oschina.net/u/3866531/blog/1939070

volatile使用场景

状态标记量

Double check

https://my.oschina.net/u/3866531/blog/1939107

volatile与synchronized使用比较

https://my.oschina.net/u/3866531/blog/1939115

转载于:https://my.oschina.net/u/3866531/blog/1939138

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值