>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 20383 个阅读者 刷新本主题
 * 贴子主题:  Java并发编程之验证volatile不能保证原子性 回复文章 点赞(0)  收藏  
作者:sunshine    发表时间:2020-03-23 17:22:30     消息  查看  搜索  好友  邮件  复制  引用

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

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

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

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

原子性操作的定义:

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

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

模拟场景:

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

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

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

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

  再来看看主线程里面:

     点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

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

     我们分别用10个线程执行100次,50个线程执行1000次以及50个线程执行一百万次来        
  线程数量  执行次数  number预期结果  实际运行结果
  10  100  10*100=1000  1000
  50  1000  五万  49297
  200  1000  二十万  194181
  50  1000000  5千万  7246921

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

              点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

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

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

我们来分析:

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

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

              说明:

主内存中有共享变量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)

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

            

----------------------------
原文链接:https://blog.51cto.com/kaigejava/2480769
作者:凯哥

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-03-23 17:22:30 重新编辑]
  Java面向对象编程-->对象的生命周期
  JavaWeb开发-->Web运作原理(Ⅲ)
  JSP与Hibernate开发-->映射一对多关联关系
  Java网络编程-->用Axis发布Web服务
  精通Spring-->通过Axios访问服务器
  Vue3开发-->Vue简介
  [求助] 如何观看孙老师的课程视频
  Java虚拟机进行类连接的原理
   JAVA进阶之IO模型深入解析
  从实战角度解读JVM:类加载机制+JVM调优实战+代码优化!
  Java如何遍历Enumeration
  Java并发编程的总结与思考
  BST 二叉搜索树
  不修改源代码,动态注入Java代码的方法
  Java是如何实现自己的SPI机制的?
  编程语言搜索量排行:用十年数据告诉你什么最受欢迎
  好消息:《Java网络编程核心技术详解》出版
  redis持久化问题处理
  Java入门实用代码:获取所有线程
  Java入门实用代码:获取当前线程名称
  通过Java读取Excel数据
  更多...
 IPIP: 已设置保密
树形列表:   
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。