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

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

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

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

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

原子性操做的定义:

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

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

模拟场景:ide

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

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

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

0fHtwDvSNXM

再来看看主线程里面:
线程

0fHtwEJOHz6

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

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


线程数量

执行次数

number预期结果

实际运行结果

10

100

10*100=1000

1000

50

1000

五万

49297

200

1000

二十万

194181

50

1000000

5千万

7246921

a320cb48b0f645b799936015caa57b67.png


0fHtwEibqTI


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

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

咱们来分析:

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

0fHtwFBhhr6


说明:

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

wx.jpg

相关文章
相关标签/搜索