在 Java 并发编程中,原子性(Atomicity)、可见性(Visibility)和有序性(Ordering) 是 Java 内存模型(Java Memory Model, 简称 JMM)中保障多线程程序正确执行的三大核心特性。它们分别解决并发中的三类关键问题:操作的完整性、变量的可见性和执行顺序的可控性。
一、原子性(Atomicity)
定义:
原子性是指一个操作是不可中断的,要么全部执行成功,要么全部不执行,不会在执行过程中被其他线程打断。
示例:
在 Java 中,像 int i = 1;
这种基本赋值操作是原子性的。但像 i++
这种复合操作(读取、加1、写入)就不是原子的,因为它由多个步骤组成。
int i = 0;
i++; // 不是原子操作,包含三个步骤:读取i、加1、写入i
如何保证?
- 使用
synchronized
关键字 - 使用
java.util.concurrent.atomic
包下的原子类(如AtomicInteger
、AtomicLong
) - 使用
Lock
(如ReentrantLock
)
二、可见性(Visibility)
定义:
可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。
为什么会出现不可见?
因为每个线程都有自己的工作内存(缓存),线程对变量的操作通常是在工作内存中进行的,而不是直接操作主内存。如果没有同步机制,其他线程可能读取到的是过期的值。
如何保证?
- 使用
volatile
关键字(强制读写主内存) - 使用
synchronized
(进入同步块前清空工作内存,退出时刷新到主内存) - 使用
Lock
(同上) - 使用
java.util.concurrent
包下的并发工具类
三、有序性(Ordering)
定义:
有序性是指程序执行的顺序按照代码的先后顺序执行。但在并发环境下,编译器和处理器为了优化性能,可能会对指令进行重排序(Instruction Reordering),这在多线程下可能导致错误。
举例:
int a = 1;
int b = 2;
这两个赋值操作可能被重排序为:
int b = 2;
int a = 1;
虽然在单线程中不影响结果,但在多线程中可能会导致其他线程观察到不一致的状态。
如何保证?
- 使用
volatile
(禁止指令重排序) - 使用
synchronized
(建立内存屏障) - 使用
final
关键字(final变量在构造函数中赋值不会被重排序) - 使用
java.util.concurrent
包中的并发工具
三者关系总结:
特性 | 作用 | 保证手段 |
---|---|---|
原子性 | 操作不可中断,要么全做,要么全不做 | synchronized 、AtomicInteger 、ReentrantLock |
可见性 | 一个线程对共享变量的修改对其他线程可见 | volatile 、synchronized 、Lock |
有序性 | 禁止指令重排序,保证执行顺序 | volatile 、synchronized 、final |
实际应用示例
public class VisibilityExample {
private volatile boolean flag = false;
public void toggle() {
flag = true; // volatile保证可见性和有序性
}
public void loop() {
while (!flag) {
// 等待flag变为true
}
System.out.println("Flag is true now.");
}
}
在这个例子中,volatile
保证了:
- 可见性:一个线程修改了
flag
,另一个线程能立即看到。 - 有序性:防止编译器/处理器对
flag = true
的赋值进行重排序。
总结
- 原子性:保证操作的完整性,防止中途被中断。
- 可见性:保证线程之间对共享变量状态的同步。
- 有序性:防止指令重排序导致的执行顺序混乱。
理解这三大特性是掌握 Java 并发编程的关键,它们共同构成了 Java 内存模型的基础,帮助开发者写出正确、高效、线程安全的程序。
THE END