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)
相关推荐
│ Java并发编程.png │ ppt+源码.rar │ 高并发编程第二阶段01讲、课程大纲及主要内容介绍.wmv │ 高并发编程第二阶段02讲、介绍四种Singleton方式的优缺点在多线程情况下.wmv │ 高并发编程第二阶段03讲、...
Java并发编程实战 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及...
在Java并发编程中,volatile关键字扮演着非常重要的角色,它可以确保变量的可见性和原子性。在多线程环境下,volatile关键字可以确保变量的修改对其他线程是可见的。本文将通过示例代码详细介绍Java并发volatile可见...
### Java并发编程实践——第5章 数据冲突及诊断工具MTRAT #### 5.1 如何避免数据冲突 在并发编程中,数据冲突是指多个线程对同一资源的访问而导致的问题,这些问题可能会破坏程序的一致性和正确性。为了避免数据...
│ Java并发编程.png │ ppt+源码.rar │ 高并发编程第二阶段01讲、课程大纲及主要内容介绍.wmv │ 高并发编程第二阶段02讲、介绍四种Singleton方式的优缺点在多线程情况下.wmv │ 高并发编程第二阶段03讲、...
在Java并发编程中,有几个核心知识点是必不可少的: 1. **线程基础**:Java通过`Thread`类和`Runnable`接口来创建和管理线程。了解如何启动、停止以及同步线程是非常基础且重要的。 2. **并发工具类**:Java并发库...
总结,volatile是Java并发编程中不可或缺的一部分,它提供了一种相对轻量级的同步机制,解决了数据可见性的问题,但在处理需要原子性的操作时需要额外的同步措施。理解并合理使用volatile,可以帮助开发者编写出更加...
5. **原子类(Atomic*)**:如`AtomicInteger`、`AtomicLong`等,它们提供了原子性的读写操作,可以在不使用锁的情况下保证数据的一致性。这些类对于实现高效的并发控制非常有用。 6. **Fork/Join框架**:基于工作...
1. **Java并发编程**:Java提供了丰富的并发工具,如synchronized、volatile、ConcurrentHashMap、CountDownLatch等,用于保证多线程环境下的数据一致性。在秒杀场景中,通常会用到这些工具来控制并发访问的数量,...
这个资料包包含了丰富的源代码示例,旨在帮助开发者深入理解Java并发编程的核心概念和技术。以下是这个主题中涉及的一些关键知识点: 1. **线程与并发基础**:Java中的并发是通过线程实现的,了解线程的创建(如`...
总结起来,`volatile`关键字在Java中扮演了保证线程之间数据可见性的角色,但它不保证原子性。在编写多线程代码时,正确理解和使用`volatile`可以帮助避免因数据不一致引发的问题,但面对复杂的并发场景,可能还需要...
但volatile不能保证原子性,所以对于复合操作(如加减操作),仍然需要同步。 - **Lock接口与ReentrantLock类**:Java提供更细粒度的锁控制,如`java.util.concurrent.locks.Lock`接口及其实现类`ReentrantLock`。...
23. **JUC(Java并发编程工具包)**:包括AQS(AbstractQueuedSynchronizer)、CountDownLatch、CyclicBarrier、Semaphore等工具,简化并发编程。 24. **定时调度与循环调度**:可以使用ScheduledExecutorService...
Java虚拟机(JVM)是Java编程语言的核心组成部分,它为Java程序提供了...通过深入学习这本书,开发者可以更好地理解和优化Java应用程序,解决内存管理、性能调优、并发编程等方面的问题,从而提升代码质量与系统性能。
volatile保证了变量在多线程环境下的可见性和有序性,但并不保证原子性。例如,对于volatile修饰的long或double变量,其读写操作是原子的,避免了因数据宽度导致的并发问题。面试官可能会通过询问如何创建volatile...
### Java成神之路:核心知识点详解 #### 一、JVM与内存管理 **1. JVM内存结构** - **堆**: 所有线程共享的主要内存区域,用于存放对象实例和数组。 - **栈**: 每个线程在创建时都会创建一个栈空间,用于存放局部...
- **volatile 关键字**:保证了变量的可见性,但不保证原子性。 - **Lock 接口**:提供了比 synchronized 更细粒度的锁控制,如 `ReentrantLock`。 掌握这些知识点对于1~3年经验的Java后端开发者来说至关重要,...
这意味着如果多个线程试图同时修改数组的不同元素,volatile关键字并不能确保所有操作都是原子性的。例如,如果有两个线程分别尝试修改数组的两个不同索引位置的值,这两个操作不会互相干扰。 2. **volatile是否能...
- **注意事项**:仅保证可见性和有序性,不保证原子性。 #### 二、并发与多线程 1. **创建线程的方式** - 继承`Thread`类 - 优点:简单直观。 - 缺点:Java不允许一个类继承多个类,因此这种方式限制了类的...
- `volatile`关键字确保变量在所有线程间可见性,但不保证原子性。 - `Atomic*`类如`AtomicInteger`提供原子操作,用于实现高效并发。 - `ConcurrentHashMap`等并发集合提供了线程安全的数据结构。 3. **反射与...