Java多线程原子性、可见性与有序性深度解析
在Java多线程开发中,理解并发的三大核心特性至关重要。本文将详细解析什么是原子性、可见性和有序性,剖析它们在JVM层面的运作机制以及可能导致的并发安全问题,并提供相应的解决方案与判断标准。
1. 原子性问题
所谓原子操作,即“不可中断的一个或一系列操作”。它意味着操作一旦开始,就会一直运行到结束,中间不会被任何线程调度机制打断,也不会发生线程切换。
以经典的 i++ 操作为例,它在 JVM 层面并非单步指令,而是会拆分为以下四步字节码执行:
getstatic i // 获取静态变量i的值(从内存取值)
iconst_1 // 准备常量1
iadd // 自增(寄存器增加1)
putstatic i // 将修改后的值存入静态变量i(存值到内存)
在单线程环境下,这四行代码顺序执行,不会交错,因此没有问题。但在多线程场景下,这四个原子操作之间可能发生线程切换,导致结果不一致。
当多个线程同时执行这些步骤时,可能会出现以下两种典型错误情况:
- 出现负数的情况:由于读取和写入的时间差,导致数据覆盖异常。
- 出现正数的情况:部分更新被其他线程读取,导致状态混乱。
总结原子性问题:虽然“内存取值”、“寄存器增加1”和“存值到内存”本身是不可再分的原子操作,但当它们组合在一起形成复合操作(如先读后写)时,就不再具备原子性。因为操作间隙允许其他线程介入,从而导致读写数据不一致。
2. 可见性问题
内存可见性是指:当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。如果无法立即看到,即为可见性问题。
底层原理:Java内存模型 (JMM)
JMM 规定了所有变量都存放在公共主存(Main Memory)中。每个线程拥有自己的私有工作内存(Working Memory),包含该线程使用到的变量的副本。线程对变量的所有读写操作都必须在工作内存中进行,不能直接读写主存。
可见性冲突示例:
- 主存中
sum=0。 - 线程A读取
sum=0到工作内存,将其加1变为1。此时线程A尚未将新值刷回主存。 - 线程B紧接着从主存读取
sum,发现仍为0。线程B也将其加1变为1并尝试写回。 - 最终两个线程操作完,期望结果是
2,但实际结果可能是1。
这种并发操作下的失效如下图所示:
解决方法:
要解决这个问题,必须保证线程间的通信,即一个线程修改了变量后,能通知其他线程刷新主存。在 Java 中,最简单的方式是使用 volatile 关键字修饰共享变量,强制线程直接从主存读写数据,并保持缓存一致性协议。
3. 有序性问题
有序性是指程序按照代码编写的先后顺序执行。如果在并发环境下,程序的实际执行顺序与预期顺序不同,并且导致了逻辑错误,这就是有序性问题。
指令重排序的影响:
CPU 为了提高执行效率,可能会进行指令重排序。在单核时代,这不会影响最终结果的正确性;但在多核并发环境下,若多个线程共享可变数据,指令重排序会导致严重的逻辑漏洞。
通过以下代码演示有序性问题的极端情况:
@Slf4j
public class Test3 {
private static volatile int x=0,y=0;
private static int a=0,b=0;
public static void main(String[] args) throws InterruptedException {
for(int i=0;;i++){
a=0; b=0; x=0; y=0;
Thread t1 = new Thread(() -> {
a = 1;
x = b; // 注意这里读取的是b
});
Thread t2 = new Thread(() -> {
b = 1;
y = a; // 注意这里读取的是a
});
t1.start(); t2.start();
t1.join(); t2.join();
// 理论上x和y不可能同时为0
if(x==0 && y==0){
log.debug("x:{}, y:{}",x,y);
}
}
}
}
理论上,无论哪个线程先执行,结果应该是 (1,0)、(0,1) 或 (1,1),绝对不可能出现 (0,0)。然而实际运行中,确实观测到了 x:0, y:0 的输出。
原因分析:
这是因为发生了指令重排序。例如线程 t1 中的 a=1 和 x=b 的顺序可能被交换,导致 x=b 先执行(此时 b 为0),然后 a=1 才执行。同理 t2 也可能发生类似重排,最终导致双零输出。
总结
并发程序的正确执行依赖于原子性、可见性和有序性的保障。只要其中任意一项未能得到妥善控制,都可能导致难以排查的并发错误。在实际开发中,应深刻理解这三者的机制,合理使用锁、volatile 或 java.util.concurrent 包下的高级工具来规避风险。
以上就是Java多线程的原子性、可见性与有序性的详细内容,更多关于Java并发编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













