Java并发编程之验证volatile不能保证原子性

通过系列文章的学习,凯哥已经介绍了volatile的三大特性。1:保证可见性 2:不保证原子性 3:保证顺序。那么怎么来验证可见性呢?本文凯哥(凯哥Java:kaigejava)将通过代码演示来证明为什么说volatile不能够保证共享变量的原子性操作。

我们来举个现实生活中的例子:

中午去食堂打饭,假设你非常非常的饥饿,需要一荤两素再加一份米饭。如果食堂打饭的阿姨再给你打一个菜的时候,被其他人打断了,给其他人打饭,然后再回过头给你打饭。你选一荤两素再加一份米饭打完的过程被打断了四次耗时30分钟。你想想你自己的感受。是不是要疯了,要暴走了!其实,如果把从你点菜到阿姨给你打完饭这个过程,看着计算机的一个线程执行过程的话,那么在你点菜到你拿到饭菜这个过程是一个完整的,不能被打断的,这就是所谓的原子性。如果被多次打断的话想想你的心理,就知道程序如果在执行过程被打断后的结果了。

原子性操作的定义:

所谓的原子性操作就是线程对变量的操作一旦开始,就会一直运行直到结束。中介不会因为其他原因而切换到另一个线程。操作是不可分割的,在执行完毕之前是不会被其他任务或是事件中断的。一个操作或者是多个操作要么执行都成功要么执行都失败(可以结合数据库的原子性理解)。

怎么证明volatile修饰的共享变量就不能保证原子性呢?

模拟场景:

共享变量volatile int number=0;执行number++操作。使用多个线程多次调用。看看使用volatile修饰的number在执行结束后的结果是否是我们预期的结果。

我们分别用10个线程执行100次,50个线程执行1000次以及50个线程执行一百万次来看看结果。

先来看看变量是用volatil修饰的

再来看看主线程里面:

按照上面咱们规定的线程数量运行次数来看看咱们预期结果和实际运行结果:

我们分别用10个线程执行100次,50个线程执行1000次以及50个线程执行一百万次来


线程数量


执行次数


number预期结果


实际运行结果


10


100


10*100=1000


1000


50


1000


五万


49297


200


1000


二十万


194181


50


1000000


5千万


7246921

从上面表格中我们可以看到,即时共享变量用volatile修饰了。但是随着线程数量或者执行次数的增加,实际运行结果与预期结果相差越来越大。如果预期结果和运行结果一致则说明保证了原子性,但是从结果来看不是这样的。从而证明了volatile的第二个特性:不能保证原子性。

为什么从i++的运行结果上就能看出不保证原子性呢?

我们来分析:

正常来说200个线程,每个线程执行了1000次。最后应该输出的是:200*1000=20000.二十万。但是实际结果却不是二十万次。那说明了什么呢?请看下图:

说明:

主内存中有共享变量number的值是0,现在有4个CPU带着4个线程都从主内存中copy变量到自己的工作区。这个是CPU1先竞争到然后再线程1的工作区中执行了number++.执行后将number的值更新成了1,写回到主内存中了。这个时候正要或者正在通知其他CPU主内存中的number值变化了。CPU2和CPU3都收到通知了,将自己工作区的变量置为无效,重新从主内存获取到number=1的值。这个时候CPU4执行的也快,在还没有收到CPU1的通知的时候,就将自己运行后的number++的值也写回到了主内存中。其实这个时候,cpu1线程1的操作还在进行中,但是因为cpu4线程4的操作打断了线程1的操作。第一轮运行结果应该是4,但是因为线程4把线程1执行打断了,将线程1执行结果覆盖了。所以实际执行后的效果有可能是3或者2但是不可能是4.

从上分析结果,我们更能理解到volatile修饰的共享变量不能保证原子性了。因为有可能被其他线程打断执行。

怎么解决原子性问题呢?可以使用juc包下的atomic包下的对象就可以了。

Volatile的有序性证明,欢迎学习下一篇:《Java并发编程之验证volatile指令重排-理论篇》

欢迎关注凯哥公众号:凯哥Java(kaigejava)

原文地址:https://www.cnblogs.com/kaigejava/p/12544060.html

时间: 2024-11-03 20:59:51

Java并发编程之验证volatile不能保证原子性的相关文章

java并发编程--深入理解volatile关键字

??volatile是一个在java并发编程中耳熟能详的关键字.即使从来没有使用过,你也偶尔会在技术书籍或博客中见到.对volatile关键字的解释常常被一笔带过:被修饰的变量具有可见性,但不能保证原子性.但是到底如何保证可见性,可见性是什么--诸如此类的问题在碰到这种凝练的解释时会产生一种知其然不知其所以然的困惑.那么我将尽力在这一篇文章中将volatile关键字的意义彻底击穿. 可见性 ??可见性,顾名思义,可见程度.假如你在被窝里打开了手电筒,只有你自己知道手电筒打开了,那么可见性就非常差

Java并发编程里的volatile。Java内存模型核CPU内存架构的对应关系

CPU内存架构:https://www.jianshu.com/p/3d1eb589b48e Java内存模型:https://www.jianshu.com/p/27a9003c33f4 多线程下的缓存一致性问题:https://www.jianshu.com/p/97dc5242c3a6 java volatile关键字解惑:https://www.jianshu.com/p/195ae7c77afe Google-Java Memory Model:https://www.jianshu.

[Java 并发编程实战] 对 volatile 变量进行实例验证(内含源码)

「 天行健,君子以自强不息.地势坤,君子以厚德载物.」---<易经> volatile 变量,在上一篇文章中已经有简单提及相关概念和用法,这一篇主要对 Volatile 变量的特性进行源码验证.验证它的涉及到的三个特性: 可见性 指令重排序 非原子性 volatile 之可见性验证 上一篇文章中,讲到 volatile 变量通常被当做状态标记使用.其中典型的应用是,检查标记状态,以确定是否退出循环.下面我们直接举个反例,源码如下: 1 public class Volatile { 2 3 b

Java并发编程 Volatile关键字解析

volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的. 2)禁止进行指令重排序. 根据volatile的语义,我们可以看到,volatile主要针对的是并发三要素(原子性,可见性和有序性)中的后两者有实际优化作用. 可见性: 线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作.

6、Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatil

java并发编程(2)--volatile

转载:http://ifeve.com/volatile/ 作者:方 腾飞 花名清英,并发网(ifeve.com)创始人,畅销书<Java并发编程的艺术>作者,蚂蚁金服技术专家.目前工作于支付宝微贷事业部,关注互联网金融,并发编程和敏捷实践. Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”. 可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值. 共享变量:在多个线程之间能够被共享的变量被称为共享变量.共享变量包括所有的实

Java 并发编程:volatile的使用及其原理

Java并发编程系列[未完]: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) Java 并发编程:volatile的使用及其原理 一.volatile的作用 在<a href="http://www.cnblogs.com/paddix/p/5374810.html">&

Java并发编程底层实现原理 - volatile

Java语言规范第三版中对volatile的定义如下: Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致性的更新,线程应该确保通过排他锁 单独获得这个变量. volatile有时候比锁更加方便,比如一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的 值是一致性的. volatile是如何来保证可见性的?  需要查看Java代码转换成汇编代码之后,具体执行的过程可参考<Java并发编程的艺术> 第二章,或者其他资料.(主要是我对汇编不太熟) 还涉

JAVA并发编程4_线程同步之volatile关键字

上一篇博客JAVA并发编程3_线程同步之synchronized关键字中讲解了JAVA中保证线程同步的关键字synchronized,其实JAVA里面还有个较弱的同步机制volatile.volatile关键字是JAVA中的轻量级的同步机制,用来将变量的更新操作同步到其他线程.从内存可见性的角度来说,写入volatile变量相当于退出同步代码块,读取volatile变量相当于进入同步代码块. 旧的内存模型:保证读写volatile都直接发生在main memory中. 在新的内存模型下(1.5)