回答
所谓原子性,就是一个操作或者多个操作,要么完全执行,要么完全不执行,在执行过程中是不能被其他因素打断或者插入。
volatile 我们知道它能保证可见性和有序性的,但是对于原子性,它无法保证。
详细分析
原子性
原子性的核心概念就两次:完整、不可分割。也就是某个线程在正在做某个业务时,要么它这个业务完整执行完,要么完全不执行,不允许处于一个中间状态。
我们最最经典的原子操作是银行账户转账:从账户 A 向账户 B 转入 10000 元,它只允许存在两种情况:
- 转账成功:从账户 A 扣掉 10000 元,给账户 B 增加 10000 元。
- 转账失败:账户 A 、账户 B 均没有变化。
不可能存在一个所谓的中间状态,如从账户 A 扣掉 10000 元,但是账户 B 不增加 10000 元。
我们看下面这段代码:
i = 1; // 1
j = i; // 2
i++; // 3
i = j + 1; // 4
这四句代码中哪些是原子操作,哪些不是,首先在单线程环境下,我们可以认为上面四个全部都是原子操作,但是多线程,这四句代码情况如下:
i = 1;
:是原子操作。赋值操作通常是原子的,因为它只涉及单个步骤,即将一个值写入到一个变量中。j = i;
:不是原子操作。因为这段可以分为两个步骤,读取 i,赋值 j。i++;
:不是原子操作,它包含了三个步骤:读取 i,执行 i + 1,新值写入 i。i = j + 1
:与 i++ 一样,包含了三个步骤。
其实在多线程环境下,Java 只保证基本类型的赋值操作是原子性,但这也仅限于单个变量且没有涉及到 64 位的 long
和 double
类型。
volatile 能保证原子性吗?
我们先看一个简单的例子:
public class SkTest {
private int num = 0;
public void addNum() {
num++;
}
public int getNum() {
return num;
}
public static void main(String[] args) {
SkTest test = new SkTest();
for (int i = 0; i < 20; i++) {
new Thread(() ->{
for (int j = 0; j < 10000; j++) {
test.addNum();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("num 的值为:" + test.getNum());
}
}
num 是普通变量,num++ 又不是原子操作,那么在多线程环境下,肯定不是线程安全的,我们执行5次,结果分别为:
num 的值为:108691
num 的值为:46885
num 的值为:135318
num 的值为:45002
num 的值为:34058
那在 num 前面加上 volatile 呢?
public class SkTest {
private volatile int num = 0;
// 省略其他代码
}
执行 5 次结果:
num 的值为:65806
num 的值为:115472
num 的值为:73228
num 的值为:109038
num 的值为:114220
为什么会出现这种情况呢?还是 volatile 的语句决定的,volatile 语义如下:
当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值立即刷新到主内存中。 当读一个 volatile变量时,JMM 会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量
即保证可见性,但不保证原子性。大明哥用一张图来解释下上图示例你就清晰了:
JMM 对原子性问题的保证
- 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,但是 long 和 double 是非原子性协定,在某些场景下,它并不是原子性的。
- 锁的原子性保证:在Java中,锁(
synchronized
或者lock
锁)可以保证在同一时刻只有一个线程能执行一个同步块的代码。它保证了在同步块内的操作是原子性的,对共享变量的所有读取和写入都是原子性的。 - Atomic类的原子操作:Java 提供了一套原子类,如
AtomicInteger
、AtomicLong
等,它们利用了CAS 操作来实现变量操作的原子性。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。