并发的原子性 可见性 有序性

本文详细解析了并发编程中的三个核心概念:原子性、可见性和有序性,并对比了synchronized和volatile关键字的区别与联系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、原子性(Atomicity)

定义:即一个操作或者多个操作,要么全部执行并且不被打断,要么就都不执行。

关键字:synchronized

比如:从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
综上可知,对变量的写操作不依赖于当前值才是原子级别的,在多线程环境中才可以不用考虑多并发问题。比如:n=n+1、n++ 就不行。n=n+1才是原子级别的,实在没把握就使用synchronized关键字来代替volatile关键字。

二、可见性(Visibility)

定义:当一个线程修改了共享变量的值,其他线程会马上知道这个修改。当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从缓存中读取。

关键字:volatile、synchronized、final

三、有序性(Ordering)

定义:虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。

关键字:volatile、synchronized

volatile本身就包含了禁止指令重排序的语义,而synchronized关键字是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则明确的。

重排序是一种性能上的优化。
如下:因为第一行和第三行有依赖关系,第二行没有任何的依赖关系,所以我们的编译器会认为把第二行重排序后与结果是没有影响的。

int a = 1;                				int b = 1;						
int b = 1;		重排序后代码执行顺序:	int a = 1;
a = a + 1;				  				a = a + 1;

小结:

synchronized关键字同时满足以上三种特性,但是volatile关键字不满足原子性。
在某些情况下,volatile的同步机制的性能确实要优于锁(使用synchronized关键字或java.util.concurrent包里面的锁),因为volatile的总开销要比锁低。
我们判断使用volatile还是加锁的唯一依据就是volatile的语义能否满足使用的场景(原子性)

### 原子性可见性有序性的深入解析 #### 一、原子性 原子性指的是一个操作在多线程环境中执行时,其行为是不可分割的,即该操作要么完全被执行,要么完全没有被执行。这可以防止其他线程观察到中间状态或部分更新的状态[^1]。 在Java中,某些基本的操作(如单个读取或写入long/double类型的变量)可能不是原子的,而大多数简单的赋值操作则是原子的。然而,在涉及复合操作的情况下(例如`number++`),这些操作实际上由多个步骤组成:读取当前值、增加数值以及重新存储新值。因此,如果没有额外的同步措施,则可能导致数据竞争问题[^2]。 为了确保原子性,可以通过以下方式实现: - 使用`synchronized`关键字锁定一段代码区域。 - 利用`java.util.concurrent.atomic`包下的类,比如`AtomicInteger`等,它们提供了无锁的高效解决方案[^3]。 ```java import java.util.concurrent.atomic.AtomicInteger; public class AtomicExample { private AtomicInteger atomicInt = new AtomicInteger(0); public void increment() { atomicInt.incrementAndGet(); } } ``` --- #### 二、可见性 可见性指当一个线程修改了某个共享变量的值之后,另一个线程能够立即看到这个最新的变化。默认情况下,Java内存模型并不保证这一点,因为编译器可能会对指令重排序,或者CPU缓存机制延迟刷新导致不同线程间的数据视图不一致。 通过声明变量为`volatile`,可以在一定程度上解决这个问题。标记为`volatile`的变量会在每次访问时都从主存加载最新值,并且不会被本地寄存器缓存。但是需要注意的是,尽管`volatile`能提供可见性保障,但它无法单独满足原子性需求。 示例程序展示了即使使用了`volatile`修饰符,但由于缺乏必要的同步控制,最终打印的结果仍可能出现偏差: ```java public class VolatileVisibility { public static volatile int count = 0; public static void increase() { for (int i = 0; i < 1000; ++i) { count++; } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(VolatileVisibility::increase); Thread t2 = new Thread(VolatileVisibility::increase); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); // 输出不一定等于2000 } } ``` 上述情况发生的原因在于`count++`并非单一动作而是包含了三个独立的动作——读取旧值、计算新值和保存回原位置,这三个过程之间可能存在干扰。 --- #### 三、有序性 有序性涉及到程序执行顺序与实际运行时发生的事件之间的关系。由于现代处理器架构支持乱序执行以提高性能,再加上JVM内部也可能进行一些优化调整(如方法内联、循环展开等),所以程序员所写的源码未必严格按照书写顺序去真正运行[^4]。 具体来说,存在两种主要形式的重排现象: 1. 编译期重排:编译器基于特定规则改变语句的实际排列; 2. CPU级重排:硬件层面出于效率考虑所做的变动。 针对这种情况,Happens-Before原则成为理解并维护正确因果链条的重要依据之一。它规定了几种场景下必然存在的先后关联,其中包括但不限于监视器解锁先于同一对象上的加锁等等。 一种常见的实践手段就是借助显式的屏障结构来强制维持预期逻辑流走向稳定方向发展下去。例如前面提到过的锁粗化技术正是利用减少频繁开关代价从而间接达成目的实例表现形式而已。 --- ### 总结 综上所述,在并发编程领域里头,“原子性”关注如何保护关键区段免受外部打扰破坏整体一致性;“可见性”强调跨进程通讯时候消息传递及时准确的重要性;至于最后提及的那个所谓“有序性”,则更多侧重探讨底层物理设备运作规律对我们高层抽象设计带来的潜在影响因素考量方面内容[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值