在阅读多线程书籍的时候,对volatile的原子性产生了疑问,问题类似于这篇文章所阐述的那样。经过一番思考给出自己的理解。
我们知道对于可见性,Java提供了volatile关键字来保证可见性有序性但不保证原子性
普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。


  背景:为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。

  • 如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。
  • 在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

总结下来

  • 第一:使用volatile关键字会强制将修改的值立即写入主存;
  • 第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
  • 第三:由于线程1的工作内存中缓存变量的缓存行无效,所以线程1再次读取变量的值时会去主存读取。

最重要的是

  • 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
  • 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

举2个例子,例子来源于这篇文章:

例子是这样的:

//线程1
boolean stop = false;
while(!stop){doSomething();
}//线程2
stop = true;

原文:这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
  下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
  那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
  但是用volatile修饰之后就变得不一样了:
  第一:使用volatile关键字会强制将修改的值立即写入主存;
  第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
  第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
到这里可能看起来没什么问题,我们来看例子2

public class Test {public volatile int inc = 0;public void increase() {inc++;}     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.inc);}
}

原文:大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。
  可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。
  这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
  在前面已经提到过,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
  假如某个时刻变量inc的值为10,
  线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;
  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。
  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
  那么两个线程分别进行了一次自增操作后,inc只增加了1。
  解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。

大家是不是有这样的疑问:“线程1在读取inc为10后被阻塞了,没有进行修改所以不会去通知其他线程,此时线程2拿到的还是10,这点可以理解。但是后来线程2修改了inc变成11后写回主内存,这下是修改了,线程1再次运行时,难道不会去主存中获取最新的值吗?按照volatile的定义,如果volatile修饰的变量发生了变化,其他线程应该去主存中拿变化后的值才对啊?”
  是不是还有:例子1中线程1先将stop=flase读取到了工作内存中,然后去执行循环操作,线程2将stop=true写入到主存后,为什么线程1的工作内存中stop=false会变成无效的?

其实严格的说,对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。在《Java并发编程的艺术》中有这一段描述:“在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。”我们需要注意的是,这里的修改操作,是指的一个操作

  • 例子1中,因为是while语句,线程会不断读取stop的值来判断是否为false,每一次判断都是一个操作。这里是从缓存中读取。单个读取操作是具有原子性的,所以当例子1中的线程2修改了stop时,由于volatile变量的可见性,线程1再读取stop时是最新的值,为true。
  • 而例子2中,为什么自增操作会出现那样的结果呢?可以知道自增操作是三个原子操作组合而成的复合操作。在一个操作中,读取了inc变量后,是不会再读取的inc的,所以它的值还是之前读的10,它的下一步是自增操作。

转载于:https://www.cnblogs.com/keeya/p/9255136.html

对volatile不具有原子性的理解相关推荐

  1. volatile不具备原子性

    1.理解原子性: 上面说到volatile不具备原子性,那么原子性到底是什么呢?先看如下代码 public class TestVolatile {public static void main(St ...

  2. java volatile 原子性_Java中volatile不能保证原子性的证明

    Java并发编程之验证volatile不能保证原子性 通过系列文章的学习,凯哥已经介绍了volatile的三大特性.1:保证可见性 2:不保证原子性 3:保证顺序.那么怎么来验证可见性呢?本文凯哥(凯 ...

  3. java volatile 原子性_Java并发编程之验证volatile不能保证原子性

    Java并发编程之验证volatile不能保证原子性 通过系列文章的学习,凯哥已经介绍了volatile的三大特性.1:保证可见性 2:不保证原子性 3:保证顺序.那么怎么来验证可见性呢?本文凯哥(凯 ...

  4. 线程安全、volatile关键字、原子性、并发包、死锁、线程池

    [线程安全.volatile关键字.原子性.并发包.死锁.线程池] 内容 线程安全 synchronized关键字\Lock接口 同步代码块 同步方法 Lock锁 高并发可见性问题 volatile关 ...

  5. java(八)-线程安全、volatile关键字、原子性、并发包、死锁、线程池

    Day08[线程状态.volatile关键字.原子性.并发包.死锁.线程池] 今日目标 线程安全 volatile关键字 原子性 并发包 死锁 线程池 教学目标 能够说出volatile关键字的作用 ...

  6. 为什么volatile不能保证原子性而Atomic可以?

    在上篇<非阻塞同步算法与CAS(Compare and Swap)无锁算法>中讲到在Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值 ...

  7. java volatile 原子性_为什么volatile不能保证原子性而Atomic可以?

    在上篇<非阻塞同步算法与CAS(Compare and Swap)无锁算法>中讲到在Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值 ...

  8. volatile 和 atomic 原子性的区别和联系

    volatile 和 atomic 原子性的区别和联系 作者:wenyinfeng 转载时,请注明原文出处,谢谢! An incorrect piece of lore that is often r ...

  9. 创建一个对象和i++是否原子性的理解

    创建一个对象和i++是否原子性的理解 以下是个人猜测,希望大牛可以指正下,万分感谢.希望能让我从深坑拉我一把 一:创建对象是否原子性猜想 1:创建一个对象 2:JVM进行类加载,会把类的所有信息存放到 ...

  10. volatile能保证原子性吗?

    volatile不能保证原子性. 当跟自增操作一起时,自增操作本身不是原子性操作. class Data {public volatile int number;public void add(){n ...

最新文章

  1. CAD—定义委托异步添加实体
  2. python缩进编码教程_python基础语法教程:行与缩进
  3. 学习 shell脚本之前的基础知识
  4. 【LeetCode笔记】148. 排序链表(Java、归并排序、快慢指针、双重递归)
  5. 强封锁之后,华为正寻求10亿美元贷款;苹果发布iOS 12.3.1正式版,出击垃圾短信;联想CFO为“联想搬出中国”言论道歉;...
  6. Django讲课笔记01:Django简介
  7. Unix/Linux shell脚本编程学习--Shell Script II
  8. Go 语言的垃圾回收算法被吹过头?与Java比如何?
  9. 1136 A Delayed Palindrome(20 分)
  10. 畅捷通服务器系统,畅捷通
  11. ansible之判断语句jinja2模板的使用 与roles角色的配置使用
  12. 使用stata临床决策曲线进行外部模型验证
  13. EF提示一例对一个或多个实体的验证失败。有关详细信息,请参阅“EntityValidationErrors”属性的解决
  14. 刀片服务器性能对比,刀片服务器对比-刀锋上的较量
  15. TC源码分析一,tc命令
  16. 二级计算机等级证水平高吗,全国计算机水平一级高还是二级高
  17. 图形图形处理方面的一位微软专家的主页,
  18. android通讯录demo
  19. Currency Translation in Bex Query Variable 1
  20. web前端学习584-610(JavaScript流程控制-循环---for循环 while循环 do...while循环 continue break)

热门文章

  1. 使用 Flink Hudi 构建流式数据湖平台
  2. 网易:Flink + Iceberg 数据湖探索与实践
  3. 改 3 行代码不应该花一整天的时间
  4. 95后频频离职,是员工本人问题?
  5. 城市轨道交通运营管理属于什么院系_2020年报考山东交通职业学院城市轨道交通运营管理专业怎么样...
  6. flutter字体不跟随系统_Flutter小技巧总结之flutter 适配宽高,字体
  7. 计算机管理固件在哪里,itunes固件位置在哪里
  8. 怎样设置电脑壁纸_怎样把C盘设置成禁止安装任何软件?教你两个方法,告别电脑卡顿...
  9. labview 一个tdms文件 两个线程同时往里写_LabVIEW 状态图(Statechart)介绍
  10. 决策树算法python源代码_CART决策树(Decision Tree)的Python源码实现